Available since: v3.0.0
A sequence is used to generate reliable, monotonically increasing numeric sequences in both single-node and clustered SurrealDB deployments. It uses a batch-allocation strategy to minimise coordination while guaranteeing global uniqueness.
The key features of a sequence are as follows:
Batch allocation: Nodes request ranges of sequence values at once, reducing network chatter and coordination overhead.
Node ownership tagging: Every batch is tagged with the requesting node's UUID to prevent overlap between nodes.
Durable Persistence: Sequence metadata is stored in the underlying key-value store to survive restarts and network partitions.
Concurrent, thread-safe access: A DashMap caches active sequences, allowing lock-free reads on the hot path.
Exponential back-off with full jitter: When a batch-allocation attempt fails, the node retries with an exponential delay that includes full jitter to avoid thundering-herd effects across the cluster.
Automatic cleanup: Listens for namespace and database-removal events and purges the corresponding sequence state.
The sequence implementation avoids contention by having each node reserve a range of sequence values, allowing it to serve multiple requests locally without requiring distributed coordination for every request. When a node exhausts its allocated range, it acquires a new batch from the distributed store.
Statement syntax
DEFINE SEQUENCE [ OVERWRITE | IF NOT EXISTS ] @name [ BATCH @batch ] [ START @start ] [ TIMEOUT @duration ]Examples
A sequence can be created with nothing more than a name.
DEFINE SEQUENCE mySeq;The BATCH, START, and TIMEOUT clauses can be included to configure the sequence.
DEFINE SEQUENCE mySeq2 BATCH 1000 START 100 TIMEOUT 5s;
sequence::nextval('mySeq2');
//- 100
DEFINE SEQUENCE mySeq3 BATCH 1000 START 100 TIMEOUT 0ns;
-- Possible output, since the timeout may or may not be exceeded
sequence::nextval('mySeq3');
//- 'The query was not executed because it exceeded the timeout'Sequences are never rolled back, even in a failed transaction. This differs from an approach like a single record with a manually incrementing value.
DEFINE SEQUENCE seq;
CREATE my:counter SET val = 0;
sequence::nextval("seq"); -- 0
my:counter.val; -- 0
BEGIN TRANSACTION;
sequence::nextval("seq"); -- 0
UPDATE my:counter SET val += 1; -- my:counter.val = 1
CANCEL TRANSACTION;
-- my:counter.val now rolled back to 0
sequence::nextval("seq"); -- 2
UPDATE my:counter SET val += 1; -- 1Sequence position and exports
Available since: v3.2.5
For each sequence, INFO FOR DB STRUCTURE shows a start and a resume value. start is the value that the definition declares. resume is the lowest value that no node has reserved yet. A node reserves values in batches of the sequence's BATCH size, so resume moves past a whole batch when a node reserves it.
DEFINE SEQUENCE seq BATCH 1 START 10;
sequence::nextval('seq');
sequence::nextval('seq');
sequence::nextval('seq');
LET $info = INFO FOR DB STRUCTURE;
RETURN $info.sequences;[
{
batch: '1',
name: 'seq',
resume: '13',
start: '10',
timeout: NONE
}
]An export writes each sequence with its resume value as START. After an import, the sequence continues from where it was and does not issue a value that the imported records already hold. Before 3.2.5 and 3.3.0, an export wrote the declared START, so the first sequence::nextval() after an import could repeat a value.
resume is not always the next value that sequence::nextval() returns. A node that still holds a batch it reserved earlier issues values from that batch, and those values are lower than resume. resume is the value that a sequence can safely restart from.