Two transactions request identifiers from the same database sequence. The first receives 501 and then pauses while calling another service. The second receives 502, finishes its work, and commits. A consumer looking for new rows can now see 502 while 501 is still absent.
The numbers were allocated in order, but the transactions did not complete in that order. If the consumer records 502 as its high-water mark and later reads only identifiers greater than 502, it can miss row 501 when that transaction finally commits.
A sequence number is useful only when its meaning matches the inference being made. It may identify an allocation, an event’s position in a log, a version of one account, or a document in a business numbering scheme. Those meanings require different guarantees, even when the values all look like increasing integers.
Separate Identity From Ordering
An identifier answers which object is being discussed. An ordering value answers how objects or events relate within a defined scope. One field can serve both roles, but combining them does not automatically provide every ordering property an application might need.
A database sequence can allocate distinct values to concurrent callers without proving when their transactions commit. A timestamp can describe the time observed by a particular clock without establishing causality between machines. A customer-facing invoice number can carry reconciliation expectations that are unrelated to the internal database key.
Define the scope as well. Increasing within one account is different from increasing across the entire application. A per-account version can help reject an out-of-date update without requiring an order between unrelated accounts. That smaller coordination scope is often sufficient for the business operation.
When the requirement says monotonic, ask from whose perspective. Values might increase at one generator while requests complete in a different order. A reader might see increasing committed versions while an offline replica remains behind. The contract needs to describe the observable behaviour, including failures and retries.
Allocation Order Does Not Establish Commit Order
PostgreSQL documents that sequence advances are not rolled back when a transaction aborts. Its sequences are therefore unsuitable for producing a gapless series by themselves. Gaps can arise through ordinary failed or abandoned work, rather than a missing committed business record.
The opening example illustrates a separate issue: even allocated values that eventually appear need not become visible in numerical order. A query sorted by identifier orders the rows it can see. It does not establish that all lower identifiers have already committed or will never appear.
For change capture, use a mechanism whose documented progress position represents the committed changes being consumed. A database change stream or log-based integration may offer an appropriate position, subject to its own retention and delivery rules. An arbitrary primary-key maximum is not a substitute for that contract.
Business document numbering can require a separate design. If the organisation needs to explain every issued invoice number, allocate it at a defined issuance step and retain cancellation or void records where appropriate. Applicable accounting rules vary, so do not infer legal compliance merely from an uninterrupted integer series.
A Log Position Has a Defined Scope
A log can assign positions to records as they are appended. Those positions support ordered reading and resumption within that log’s scope. They do not necessarily describe the order in which events happened in the outside world or the order in which downstream work finished.
Apache Kafka’s design documentation describes ordering within partitions. An offset belongs with its topic and partition; offset 100 in one partition is not globally earlier than offset 101 in another. Application processing can introduce further reordering if records are dispatched to concurrent workers.
Suppose a consumer reads positions 20 and 21 from the same ordered stream. Work for 21 completes first, while 20 remains in progress. Advancing a durable checkpoint past unfinished work can make recovery skip it. Either preserve ordered completion, track the outstanding work, or use a processing design that safely accounts for partial progress.
The same distinction applies to a dashboard showing the latest observed event. Observation is not necessarily application. Recording both the source position and the completed processing position makes delays and recovery easier to interpret.
Sortable Identifiers Do Not Prove Causality
Time-oriented identifiers can be useful for indexing and approximate chronological grouping. RFC 9562 defines UUID variants including UUIDv7, which incorporates a Unix-time timestamp and additional bits. Its existence means that treating all UUIDs as randomly ordered identifiers is inaccurate.
A sortable identifier still does not prove a strict global order of business events. Clock behaviour, generator implementation, concurrent generation, and comparison format all matter. Two machines can assign values that sort consistently while their recorded times do not establish which operation caused another.
Logical clocks address a different requirement. In Leslie Lamport’s paper, the happened-before relation captures causality through process order and message transmission. Logical clock values can respect that relation: if one event happens before another, its clock value is lower.
The converse does not follow. A smaller Lamport timestamp does not by itself prove that the event caused or preceded another under happened-before. A deterministic tie-breaker can extend the ordering, but that imposed order should not be presented as a measurement of elapsed time or evidence of a real-world dependency.
Detect Gaps Against a Declared Contract
A gap has meaning only when the producer and consumer agree what should be consecutive. Missing value 37 in a database sequence may be entirely normal. Missing account version 37 when applying account version 38 may indicate that a required update has not been observed.
That difference affects recovery. A consumer of per-account events might buffer version 38, request missing history, or reload an authoritative snapshot. It should not invent the missing operation or silently assume that a later version makes every earlier event irrelevant.
Repeated positions also need interpretation. A retry may redeliver an already processed event. Detecting the repeated event identity can support deduplication, while checking the entity version can protect update order. These are related checks, but a version number and an idempotency key answer different questions.
| Value | What it can represent | What should not be assumed |
|---|---|---|
| Database sequence value | Allocation from a generator | Transaction commit order or gaplessness |
| Partition offset | Position within a particular partition | Global event time or completed processing |
| Entity version | Revision within one entity’s defined history | Order across unrelated entities |
| Time-oriented identifier | Identity with sortable time-related structure | Exact causality across machines |
| Business document number | An issued document in a defined scheme | Completion of every downstream action |
If gaps trigger alerts, document the expected legitimate causes. Otherwise a normal aborted allocation can become an incident, while a genuine missed update is dismissed as routine. Monitoring needs the semantics of the sequence, not just an arithmetic check.
Plan for Reset, Failover, and Finite Ranges
A counter’s lifetime matters. Restoring an old database backup, losing allocator state, or reusing a generator identity can reintroduce values that another system has already seen. Define how uniqueness survives recovery, and keep external identifiers within the recovery assessment.
Some designs use an epoch or generation identifier alongside a local counter. That can distinguish positions from separate lifetimes, but only if the epoch itself is allocated and compared under a reliable rule. Adding another field moves the coordination requirement rather than eliminating it.
Finite counters can also wrap. RFC 1982 defines serial-number arithmetic for a particular modular comparison model, including limits on meaningful comparisons. Its rules should not be applied casually to unrelated business counters. A system needs an explicit overflow policy suited to its own protocol.
Range allocation offers another tradeoff. Giving each node a block of identifiers reduces allocation traffic but permits unused gaps and weakens any inference from numerical order to creation time. It can be a good identity strategy when those properties are acceptable; it is unsuitable when consumers secretly depend on consecutive business events.
Record the Meaning Beside the Number
Store the fields needed to interpret a position: source, partition or entity, generation where relevant, and the identifier of the event being processed. Record processing outcomes separately when source order and completion order can differ. This gives recovery enough information to resume without guessing what an isolated integer meant.
Test the failure cases that threaten the contract. Pause the holder of a lower allocated value, complete a later transaction first, redeliver a message, restore old state, and restart a consumer with unfinished work. These cases expose assumptions that a successful sequential demonstration will miss.
The same care is needed when serverless workers share external state: a shared counter is a coordination mechanism with a specific purpose, not a general record of everything the application has completed. Choosing the narrowest sufficient ordering guarantee makes that purpose easier to implement and explain.





