Skip to main content

Sequence Model and Allocation

A Kahuna sequence is a named counter. Values are monotonically increasing per sequence name, not globally across every sequence in the cluster.

State​

FieldDescription
NameThe sequence name, such as orders or tenant-a/invoices.
CurrentValueThe highest committed value for the sequence.
InitialValueThe value used as the starting point.
IncrementThe step between allocated values.
MaxValueOptional upper bound.
BlockSizeOptional per-sequence block size. If unset, the sequence follows the server-wide SequencerBlockSize.
IncarnationValue-stream generation. It increases when the sequence is updated.
RevisionSequence metadata revision.
DurabilityCurrently Persistent.
CreatedAtHybrid logical timestamp when the sequence was created.
UpdatedAtHybrid logical timestamp when the sequence was last changed.

Defaults:

  • InitialValue: 0
  • Increment: 1
  • MaxValue: null
  • BlockSize: null
  • Durability: Persistent

With the defaults, the first allocated value is 1.

The sequence record is stored under the reserved internal key __kahuna:sequences:{name}. Public key/value and lock APIs reject the __kahuna: namespace so application code cannot overwrite sequence state directly.

Next Value​

next allocates one value. The owning node usually serves it from an in-memory block that was already reserved durably.

CurrentValue = 41
Increment = 1
Next = 42

With the default block size of 1000, the first allocation for a sequence created at 0 reserves 1..1000 in one Raft commit. GetSequence then reports CurrentValue = 1000, even if only a few values from that block have been handed out. Treat CurrentValue as the reserved high-water mark, not the last value consumed by an application.

Range Reservation​

reserve allocates a contiguous range. If orders has CurrentValue = 100, Increment = 1, and the caller reserves 50, Kahuna returns:

Start = 101
End = 150
Count = 50

Range reservations for the same sequence do not overlap. After the reservation commits, the sequence current value is 150.

Block Size​

SequencerBlockSize controls how many values a sequence reserves per durable compare-and-swap. A larger block amortizes one Raft commit across more values. A smaller block reduces skipped values after restarts, evictions, or ownership changes.

A sequence can override the server-wide value with its own blockSize at create or update time:

kahuna-cli --create-sequence invoices --block-size 1
kahuna-cli --update-sequence invoices --current-value 5000 --block-size 1
kahuna-cli --update-sequence invoices --remove-block-size

Use blockSize = 1 for domains that need one durable commit per value. This is safer for strict human-facing numbering, but it is much slower than cached blocks.

Updating a Sequence​

update rewrites sequence parameters without deleting the record. It can set:

FieldEffect
currentValueNew reserved high-water mark. The next value is this value plus increment.
incrementNew positive step between values.
initialValueNew recorded starting value. It does not move the counter by itself.
maxValueNew upper bound.
removeMaxValueRemove the upper bound.
blockSizeNew per-sequence block size. Must be at least 1.
removeBlockSizeReturn the sequence to the server-wide block size.

An update starts a new Incarnation and clears stored idempotency entries. Guarantees such as uniqueness are scoped to one incarnation, so lowering currentValue can intentionally reissue numbers used by an older incarnation.

To avoid racing stale owners that may still hold an old in-memory block, Kahuna waits one SequencerBlockLease before reporting a successful update. During the same window, allocations for that sequence return MustRetry. Client and proxy deadlines for sequence updates should be longer than SequencerBlockLease, which defaults to five seconds.

If SequencerBlockLease is 0, sequence update is refused because stale blocks cannot be safely waited out.

Max Value​

If MaxValue is set and an allocation would exceed it, Kahuna returns MaxValueExceeded and does not partially allocate the request.

For example, if CurrentValue = 98, MaxValue = 100, and a caller reserves 5, the request fails because it would need 99..103.