Chapter 04 · Time, Ordering, Logical Clocks, and Versioning
Use Versions, ETags, Timestamps, and Idempotency Keys Without Pretending Time Is Perfect
Use resource versions and ETags for optimistic concurrency and idempotency keys for ambiguous retries—without treating timestamps as a universal token.
Learning outcomes
AtlasMart's API now faces two common races. Two merchandisers
read the same product and then both update it; without a
precondition, the second request can overwrite the first.
Separately, a shopper submits POST /orders, the
server commits the order, but the response is lost; a retry can
create a duplicate. These problems require different tools.
Compare monotonic version numbers, entity tags (ETags), conditional writes, timestamps, and idempotency keys by the question each one answers.
Use HTTP If-Match semantics to reject a stale update and prevent an accidental lost overwrite.
Explain why Last-Modified and ad-hoc timestamps are generally weaker concurrency validators than an exact version or strong ETag.
Implement an idempotency-key store that returns the original result for a retry and rejects key reuse with a different payload.
Document scope, persistence, expiry or deduplication window, failure atomicity, and security considerations for idempotent request processing.
1. A version number answers “which state did you read?”
A resource-local monotonic version such as
version=17 can be generated by one authoritative
write path or transaction. A client reads version 17, proposes
an update with expected version 17, and the server changes the
row only if 17 is still current. If another writer already
produced version 18, the conditional update affects nothing and
the client must reload, merge, or retry according to the domain
rule.
The strength comes from where the version is generated and compared. A random application timestamp is not equivalent to an atomic compare-and-set condition inside the system of record.
2. ETags make representation versions visible to HTTP clients
HTTP defines an entity tag (ETag) as an opaque
validator for a selected representation. With
If-Match, a client can make a state-changing
request conditional on the current representation matching one
of the supplied entity tags. RFC 9110 requires strong comparison
for If-Match and says the method must not be
performed when the condition is false. This is a natural
API-level lost-update defense.
GET /products/sku-42
200 OK
ETag: "9d04..."
PUT /products/sku-42
If-Match: "9d04..."
Content-Type: application/json
{"price":110}
# If another writer changed the representation first:
412 Precondition Failed
RFC 6585 also defines 428 Precondition Required for
servers that require clients to submit conditional requests,
specifically calling out lost-update prevention as a typical
use.
3. ETag is not necessarily a database row version
An ETag is an HTTP representation validator. It might be derived from a database version, a hash of representation bytes, or another server-controlled value. If one database entity has multiple content-negotiated representations, their validators can differ. Conversely, a database row-version token can protect storage updates without ever being exposed as HTTP.
Architecture should therefore name both layers when they differ:
“database compare-and-set on version; API exposes a
strong ETag derived from that version.” Do not assume every ETag
globally orders all versions; it is an opaque validator under
HTTP semantics.
4. Timestamps are useful context but weak default concurrency tokens
Last-Modified and application timestamps are
valuable for display, cache validation, retention, and
observability. They are usually weaker for write concurrency
because clocks can skew, timestamp resolution can collide, and
the timestamp may represent a different event than the
authoritative state change. HTTP's precondition model reflects
this distinction by providing exact entity-tag comparison in
If-Match, while date validators have different
semantics and precision.
If a timestamp is used as a concurrency token, define precisely who generates it, its resolution, monotonicity, comparison semantics, and how ties or skew are handled. A database-generated integer or version token is often easier to reason about.
5. Idempotency keys answer a different question: “is this the same logical command?”
After an ambiguous timeout, a client may not know whether a non-idempotent operation committed. An idempotency key lets the client identify a logical command across retries. The server stores the key with a request fingerprint and the result or durable operation state. Repeating the same key and payload returns or reconstructs the same outcome rather than executing the side effect again.
| Mechanism | Primary question | Typical failure prevented | Does not by itself solve |
|---|---|---|---|
| Version / CAS | is the resource still the version I read? | lost update | duplicate POST after ambiguous commit |
| ETag + If-Match | does the current HTTP representation match my validator? | accidental overwrite via HTTP | business-level duplicate command |
| Timestamp | approximately when or how old? | time/freshness workflows under stated assumptions | causal concurrency in general |
| Idempotency key | has this logical command already been processed? | duplicate side effect on retry | conflicting concurrent edits to an existing resource |
6. Idempotency requires atomicity and a scope contract
The key record and side effect must be coordinated. If AtlasMart charges a card, crashes, and only later records the idempotency key, the retry can still charge twice. A robust design stores command identity and result in the same atomic boundary where possible, or uses a downstream provider's idempotent operation identifier. The API must also define key scope such as tenant, user, and endpoint; retention or expiry; maximum payload; replay behavior; and what happens when the same key is reused with a different payload.
As of this lesson's August 2026 review, the IETF HTTPAPI
working-group Idempotency-Key Internet-Draft is
expired and archived, not an active RFC. It is useful
background, but this course does not present the header as a
standardized guarantee. A production API must document and test
its exact idempotency contract.
7. Deliberately wrong approach — use updated_at for
both lost-update detection and retry deduplication
A single timestamp does not identify which representation the editor read, and it does not identify which logical order request is being retried. Two updates can share a coarse timestamp; two retries can arrive at different times; skewed clocks can invert them. Reusing one field for all three concerns hides separate invariants behind accidental ordering.
The repair is compositional: resource version or ETag for optimistic concurrency; a server-controlled idempotency-key record for duplicate-command suppression; physical timestamps for audit and freshness where their clock assumptions are acceptable.
8. AtlasMart lab — stale ETag plus idempotent retry
from dataclasses import dataclass
import hashlib
import json
@dataclass
class Product:
id: str
price: int
version: int
updated_ms: int
def etag(p):
payload = json.dumps({"id": p.id, "price": p.price, "version": p.version}, sort_keys=True)
return '"' + hashlib.sha256(payload.encode()).hexdigest()[:12] + '"'
def conditional_update(p, expected_etag, new_price, now_ms):
if etag(p) != expected_etag:
return 412, "Precondition Failed", etag(p)
p.price = new_price
p.version += 1
p.updated_ms = now_ms
return 200, "Updated", etag(p)
product = Product("sku-42", 100, 1, 1000)
client_a_tag = etag(product)
client_b_tag = etag(product)
print("both clients read", product, "etag", client_a_tag)
print("\nclient A update")
print(conditional_update(product, client_a_tag, 110, 1100))
print("\nclient B tries stale ETag")
print(conditional_update(product, client_b_tag, 90, 1101))
print("current product:", product, "etag", etag(product))
print("\ntimestamp-only concurrency token problem")
print("two writers can share the same coarse timestamp or have skewed clocks; equality/order is not proof of same version")
processed = {}
def create_order(idempotency_key, cart):
fingerprint = hashlib.sha256(json.dumps(cart, sort_keys=True).encode()).hexdigest()
if idempotency_key in processed:
old_fp, result = processed[idempotency_key]
if old_fp != fingerprint:
return 409, "same key reused with different payload"
return 200, "replay", result
result = {"order_id": f"ord-{len(processed)+1}", "total": sum(cart)}
processed[idempotency_key] = (fingerprint, result)
return 201, "created", result
print("\nidempotency-key retry")
key = "req-7f1"
print(create_order(key, [40, 60]))
print(create_order(key, [40, 60]))
print(create_order(key, [40, 70]))
print("orders actually created:", len(processed))
Expected output
both clients read Product(id='sku-42', price=100, version=1, updated_ms=1000) etag "8363e3c4bc43"
client A update
(200, 'Updated', '"f4eaaed80382"')
client B tries stale ETag
(412, 'Precondition Failed', '"f4eaaed80382"')
current product: Product(id='sku-42', price=110, version=2, updated_ms=1100) etag "f4eaaed80382"
timestamp-only concurrency token problem
two writers can share the same coarse timestamp or have skewed clocks; equality/order is not proof of same version
idempotency-key retry
(201, 'created', {'order_id': 'ord-1', 'total': 100})
(200, 'replay', {'order_id': 'ord-1', 'total': 100})
(409, 'same key reused with different payload')
orders actually created: 1
Both clients initially hold the same ETag. Client A updates successfully and changes the representation validator. Client B's stale precondition receives 412 instead of silently overwriting A. The order command then demonstrates a replay: the same key and same payload returns the stored order, while the same key with a different payload is rejected.
Verification checklist
- The ETag is server-controlled and changes when the represented version changes.
- A stale conditional update returns 412 and leaves current state intact.
- The idempotency store fingerprints the payload as well as storing the key.
- Repeating the same logical request creates exactly one stored order.
- No claim is made that this in-memory toy provides crash durability; production storage must make deduplication state durable and atomic with the side effect.
Check your understanding
- What does If-Match protect against in a state-changing HTTP request?
- Why is an ETag not automatically the same as a database version column?
- Why are timestamps generally weaker write-concurrency validators?
- What must an idempotency-key implementation do when the same key appears with a different payload?
- Why must idempotency state be atomic or durably coordinated with the protected side effect?
Review the answers
It prevents the method from being applied when the selected representation no longer matches the client’s validator, avoiding an accidental lost overwrite.
ETag is an opaque validator for an HTTP representation; it can be derived from a row version or from other representation-specific state.
Skew, resolution collisions, ambiguous event meaning, and non-atomic comparison can make them fail to identify the exact state a client read.
Reject or otherwise flag misuse according to the API contract; treating it as the original command could hide a client bug or attack.
Otherwise a crash can commit the side effect without recording the key, or record the key without the side effect, leaving a retry able to duplicate or incorrectly suppress work.
9. Chapter summary and bridge to replication
Chapter 04 separates five concepts that are often compressed into “timestamp.” Wall clocks approximate physical or civil time and can skew. Lamport clocks preserve happens-before but cannot detect concurrency. Version vectors can detect concurrent branches under an actor model. HLCs preserve logical monotonicity while retaining physical-time structure. Versions and ETags protect conditional state changes, while idempotency keys protect logical commands across retries.
Chapter 05 uses these ordering tools inside replication patterns. Single-leader, multi-leader, and leaderless designs differ in who may write, when writes are acknowledged, how replicas catch up, and what happens when clocks or versions conflict. The next question is therefore not “which clock is best?” but “what replication topology owns the write and what evidence makes an acknowledgement durable and safe?”
Authoritative references
- RFC 9110 — HTTP Semantics: ETag, If-Match, conditional requests — normative HTTP validator and precondition semantics
- RFC 6585 — 428 Precondition Required — status code explicitly motivated by lost-update prevention
- IETF HTTPAPI — expired Idempotency-Key Internet-Draft — archived work-in-progress background; not an active RFC as of the August 2026 review
- Lamport — Time, Clocks, and the Ordering of Events — why timestamp magnitude and causality are separate concepts