dynobase-icon
Dynobase

DynamoDB Data Versioning: History, Latest Items and Safe Writes

Charuka Herath

Written by Charuka Herath

Last updated: October 23rd, 2026

Comparing item versions? Dynobase compares two items side by side, down to nested attributes, and shows before-and-after snapshots from item history. Visual item diff in Dynobase

DynamoDB does not automatically retain an item's previous versions when you overwrite it. To preserve revision history, write each revision under a distinct key and optionally maintain a separate current-item copy or pointer. A numeric version attribute used for optimistic locking prevents stale writes, but it does not create historical records by itself.

What is data versioning in DynamoDB?

There are two separate requirements often called versioning. Concurrency versioning records a token such as version: 7 on the current item so a write can reject an editor who read version 6. Revision history retains both version 6 and version 7 as separately addressable items.

A table has no switch that enables automatic application-level revision history. Your writer must create the history records, choose their identifiers and protect the latest revision from competing updates. Backups and point-in-time recovery serve recovery workflows; they are not an API for retrieving one entity's past revisions during an ordinary application request.

When should you consider data versioning?

Retain revisions when users need to compare edits, roll back a configuration, reproduce a research result, or identify which dataset and model settings produced an experiment. A research system might retain each tested combination and its outcome under an immutable revision ID. A manufacturing system can preserve each robot status change while keeping one fast lookup for the current status.

Define the access patterns first:

  • Fetch the current state by entity ID.
  • Fetch one known revision by entity ID and revision ID.
  • List revisions in order, with pagination.
  • Compare or restore selected revisions.
  • Enforce retention and access rules for old data.

Do not create a separate table for every robot or experiment. Partition keys group revisions for each entity within a shared table. The right physical layout depends on access, retention and capacity requirements, not just the word “version.”

1. Maintaining two separate tables

A resource table stores one current item per entity. A history table stores one item per revision, usually with the entity ID as its partition key and a revision number or timestamp as its sort key.

TableKeyContents
ResourcePK = ROBOT#1Current status, current revision and metadata needed by the application
ResourceHistoryPK = ROBOT#1, VERSION = 7Immutable status and context for revision 7

Use a transaction to replace the current resource and insert the new history revision together. The replacement checks the expected old revision; the history put checks that the new key is absent. If either condition fails, neither table changes. This prevents a crash between two independent writes from leaving current state without history.

Two tables can help separate permissions, retention, indexes and capacity settings. They are not inherently more expensive just because there are two names: the cost comes from stored copies, requests, indexes and provisioned capacity. A larger history table does not automatically have lower read capacity or slower point reads. Price the actual access patterns.

You can also capture history asynchronously with a stream consumer. That makes history lag behind the primary write and requires idempotent processing, retry monitoring and a recovery plan. Choose that explicitly rather than describing it as atomic dual-writing.

2. A single versioned table and metadata

A shared table can hold current state, revisions and metadata under one entity partition key. Metadata may instead live in a separate table when it has a different lifecycle or permission model. Its placement is a design choice, not a requirement of versioning.

For the robot example, power input and model identifier might be metadata; status changes are revisions. If historical interpretation depends on metadata, either snapshot it into each revision or reference an immutable metadata revision. A pointer to mutable “current metadata” can make an old experiment impossible to reproduce.

Versioning using a number

AWS describes a zero-prefix current-item pattern: keep a mutable v0_ copy alongside immutable numbered revisions. The zero prefix is an alias for the latest state, not the first historical version.

PKSKversionstatus
ROBOT#1METANot applicableRobot metadata
ROBOT#1v0_3ACTIVE
ROBOT#1v0000000001_1INACTIVE
ROBOT#1v0000000002_2STARTING
ROBOT#1v0000000003_3ACTIVE

String sort keys order lexically, so unpadded v10 sorts before v2. Choose a fixed-width numbering format and define what happens before its range is exhausted. Alternatively, use a numeric sort key if the rest of the table design supports it.

Atomically write a revision and the latest copy

This SDK v3 example uses robotTable with string PK and SK keys. Pass the current version read from v0_, or 0 for first creation. It writes both the complete latest snapshot and the immutable revision in one transaction.

Use the same token, timestamp and payload if retrying this exact request. After a version conflict, reload and decide how to reconcile; do not label an unrelated payload as the same request. The token's service idempotency window is limited, while the create-only history condition continues to prevent an existing revision from being replaced.

This example intentionally replaces the current snapshot with its complete modeled fields. Include every field you intend to retain, or use an update action for current state. Never reset the revision counter while retaining old history keys. The transaction guide explains cancellation handling and token scope.

Reading current state and history

Fetch the current item with GetItem at PK = ROBOT#1, SK = v0_. A known revision is another direct get. To list history, query the entity partition with begins_with(SK, :prefix) where :prefix is v, and omit the v0_ alias from the display.

If you filter the alias on the server, remember that Limit counts evaluated items before the filter. Continue while LastEvaluatedKey exists even when a page is empty. A single latest-item get is simpler when only current state is needed.

Timestamp-based versioning

Timestamp sort keys work well for events that arrive with an event time. Store a normalized UTC ISO 8601 value with consistent precision, or use a numeric epoch sort key. A day-first string such as 27-03-2022:08:00:50 does not sort chronologically across months and years.

This boto3 example uses a separate EVENT# key namespace within the same robotTable. The event ID breaks ties between events with the same timestamp and should remain stable on retry.

This returns the largest event-time key, not necessarily the last event ingested. Delayed events and clock skew can make those different. If you maintain a current-state copy from events, define whether newer event time, source sequence or arrival order wins. See timestamp modeling for ranges, precision and time zones.

3. Using atomic counters

An atomic counter can allocate a revision number without first reading it. It is not a history store: incrementing a field overwrites that field in place. You still need to write the payload under a distinct revision key.

For an independent counter item in robotTable:

Do not combine this independent allocation strategy with the previous expected-version writer without redesigning the protocol. If allocation succeeds and history insertion fails, a gap remains. If the allocation response is lost and you repeat it, another number is allocated. A later writer can also finish before an earlier allocator.

You cannot feed a value generated by one transaction action into another action in the same request; the transaction payload must be known upfront. AWS's version-control design examples discuss these counter and retry tradeoffs. Use a read-plus-conditioned transaction when gap-free committed revisions and a synchronized latest copy are required.

Java version attributes do not preserve history

The Java 2.x Enhanced Client's @DynamoDbVersionAttribute supplies optimistic concurrency through its VersionedRecordExtension. It checks and advances a version attribute for supported writes. It does not put historical copies into your table, and deletes need an explicit condition.

The similarly named @DynamoDBVersionAttribute belongs to the legacy Java 1.x DynamoDBMapper. For new code, use the Enhanced Client and keep your revision-history design separate. The locking guide includes the current annotation and a mapped bean example.

Data versioning best practices

Choose numeric revisions when you need an explicit sequence of accepted edits. Choose timestamped revisions when event time is the main query dimension, and include a collision strategy. Neither choice depends simply on how fast updates arrive.

Keep immutable revisions immutable, including during rollback. Restore a selected old payload by writing a new revision and advancing the current item; this preserves who performed the rollback and what it restored. Store actor, source and reason metadata where the application needs them.

Each stored revision adds storage and write costs. A current copy adds another write; a transaction adds its own capacity multiplier. Keep items within the 400 KB limit and avoid growing one ever-larger history list inside a single item.

Use TTL for retention only when asynchronous deletion is acceptable. Expired history remains readable until deleted, so readers should enforce the retention rule if it matters immediately. Do not expire a revision that a current pointer still needs, and do not assume TTL is supported on MRSC global tables.

A stream can feed archival or audit processing, but its finite retention is not permanent history. Monitor consumer progress and make writes idempotent. For multiple active Regions, define a writer or conflict strategy: MREC last-writer-wins behavior can conflict with local version checks, and MRSC does not support the transactional pattern shown above.

Frequently Asked Questions

Does DynamoDB have versioning?

DynamoDB does not automatically retain old item versions for application queries. Store revisions under distinct keys. SDK version attributes can detect stale writes, but do not preserve history.

How can I keep a previous record when writing a new version?

Write the new revision under a different sort key with a create-only condition. If you also maintain a current copy, update it and insert the revision in one transaction.

Does an atomic counter preserve old versions?

No. It allocates or changes a number in place. Your application must write separate historical items and handle allocation failures, retries and ordering.

What is the v0 item in DynamoDB versioning?

It is a convenient mutable copy of the latest revision. Numbered historical items remain immutable, while the v0 copy carries the latest revision number and payload.

Does DynamoDbVersionAttribute create revision history?

No. The Java Enhanced Client annotation supports optimistic locking on a version attribute. Historical copies require an explicit data model and writes.

Dynobase is a Professional GUI Client for DynamoDB

Start your 7-day free trial today

Product Features

Download
/
Pricing
/
Member Portal
/
Privacy
/
EULA
/
Twitter
© 2026 Dynobase