Overview
The State primitive provides named cells with compare-and-swap (CAS) semantics. Each cell holds a single value with a per-entity version counter that increments on every write. CAS enables safe concurrent updates by requiring the caller to specify the expected version.
- Version semantics:
Version::Counter(u64)- per-cell counter starting at 1, incremented on every write - Key construction:
Key { namespace: Namespace::for_branch(branch_id), type_tag: TypeTag::State (0x03), user_key: cell_name.as_bytes() } - Storage format:
Value::String(JSON)wrappingState { value, version, updated_at }struct - Transactional: Yes - all operations run inside engine transactions; supports Session-level multi-command transactions
Layer Architecture
+------------------------------------------------------------------+
| CLIENT |
| Command::StateInit { branch, cell, value } |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| SESSION (session.rs) |
| Routes to executor OR active transaction context |
| - If txn active: Transaction::new(ctx, ns).state_init() |
| - If no txn: executor.execute(cmd) |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| EXECUTOR (executor.rs) |
| Dispatches to: crate::handlers::state::state_init(...) |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| HANDLER (handlers/state.rs + bridge.rs) |
| 1. to_core_branch_id(&branch) -> core::BranchId |
| 2. validate_key(&cell) -> check empty/NUL/_strata/1024 |
| 3. Call primitives.state.init(&branch_id, &cell, value) |
| 4. extract_version(&version) -> u64 |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| ENGINE PRIMITIVE (primitives/state.rs - StateCell) |
| 1. Build Key::new_state(namespace, cell_name) |
| 2. db.transaction(branch_id, |txn| { |
| - txn.get(key): check if exists |
| - If exists: return existing version (idempotent) |
| - If not: State::new(value) with Counter(1) |
| - to_stored_value(&state) -> Value::String(JSON) |
| - txn.put(key, serialized_state) |
| }) |
| 3. Return Versioned<Version> |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| TRANSACTION (concurrency crate) |
| Single write: Key::new_state(ns, cell) -> State JSON |
| OCC validation on commit |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| STORAGE (storage/sharded.rs) |
| DashMap<Key, VersionChain> |
| State is a single versioned value per cell key |
| Multiple versions retained for history (getv) |
+------------------------------------------------------------------+
Operation Flows
StateInit (idempotent creation)
Client Handler Engine (StateCell) Transaction Storage
| | | | |
|-- StateInit ------>| | | |
| {branch,cell,val} | | | |
| | | | |
| |-- validate ------>| | |
| | cell name | | |
| | branch->UUID | | |
| | | | |
| | |-- begin txn ------>| |
| | | | |
| | |-- txn.get -------->|-- read chain ---->|
| | | Key::new_state | |
| | | (ns, cell) | |
| | | | |
| | | IF EXISTS: | |
| | | deserialize -> | |
| | | State struct | |
| | | return existing | |
| | | version (noop) | |
| | | | |
| | | IF NOT EXISTS: | |
| | | State::new(val) | |
| | | version=Ctr(1) | |
| | | to_stored_value | |
| | | -> JSON string | |
| | | | |
| | |-- txn.put -------->|-- write_set ----->|
| | | | |
| | |-- commit --------->|-- persist ------->|
| | | | |
|<-- Output::Version-|<- extract u64 ----|<- Versioned -------| |
| Counter(1) or | | <Version> | |
| existing counter | | | |
Steps:
- Handler: Converts branch, validates cell name, calls
primitives.state.init(). - Engine (StateCell): Constructs key. Opens transaction. Checks if cell exists.
- If exists: Deserializes the stored
Statestruct, returns its existingversion. No write occurs. This makesinitidempotent. - If not exists: Creates
State::new(value)which setsversion = Counter(1)andupdated_at = now(). Serializes to JSON string viato_stored_value(). Writes to storage.
- If exists: Deserializes the stored
- Returns: The version counter (1 for new cells, existing counter for already-initialized cells).
StateGet
Client Handler Engine (StateCell) Transaction Storage
| | | | |
|-- StateGet ------>| | | |
| {branch, cell} | | | |
| |-- validate ------>| | |
| | | | |
| | |-- begin txn ------>| |
| | | | |
| | |-- txn.get -------->|-- read chain ---->|
| | | Key::new_state | |
| | | (ns, cell) | |
| | | | |
| | | IF FOUND: | |
| | | deserialize -> | |
| | | State struct | |
| | | extract .value | |
| | | | |
| | | IF NOT FOUND: | |
| | | return None | |
| | | | |
|<-- Output::Maybe --|<- Option<Value> --|<- Option<Value> ---| |
Steps:
- Handler: Converts branch, validates cell name, calls
primitives.state.read(). - Engine (StateCell): Constructs key. Opens transaction. Calls
txn.get(). If found, deserializesStatefrom JSON string, returnsSome(state.value). If not found, returnsNone. - Note: Only the
.valuefield is returned to the caller. The version counter and timestamp are discarded. UseStateGetvto get version information.
Session-transaction path: When a Session transaction is active, StateGet is handled in execute_in_txn() via ctx.get(&full_key). The result is deserialized from Value::String to get the State struct, then state.value is returned. This provides read-your-writes for state cells within the transaction.
StateCas (Compare-and-Swap)
Client Handler Engine (StateCell) Transaction Storage
| | | | |
|-- StateCas ------->| | | |
| {branch, cell, | | | |
| expected, value} | | | |
| | | | |
| |-- validate ------>| | |
| | | | |
| |-- expected=None? -| | |
| | YES: init path | | |
| | NO: cas path | | |
| | | | |
| | === CAS PATH === | | |
| | | | |
| | |-- begin txn ------>| |
| | | | |
| | |-- txn.get -------->|-- read chain ---->|
| | | Key::new_state | |
| | | | |
| | |<- State struct ----| |
| | | | |
| | |-- compare -------->| |
| | | current.version | |
| | | == Counter(exp)? | |
| | | | |
| | | MISMATCH: | |
| | | Err(conflict) | |
| | | | |
| | | MATCH: | |
| | | new_ver = | |
| | | Counter(exp+1) | |
| | | State { value, | |
| | | new_ver, now } | |
| | | | |
| | |-- txn.put -------->|-- write_set ----->|
| | | | |
| | |-- commit --------->|-- persist ------->|
| | | | |
|<-- MaybeVersion ---|<-----------------|<- new_version ------| |
| Some(exp+1) or | (None on fail) | | |
| None | | | |
Steps:
- Handler: Two paths based on
expected_counter:expected_counter = None: Init semantics. First reads the cell. If it already exists, returnsOutput::MaybeVersion(None). If it doesn’t exist, callsstate.init().expected_counter = Some(n): CAS path. Callsstate.cas()withVersion::Counter(n).
- Engine (StateCell) - CAS path: Opens transaction. Reads current state. If cell doesn’t exist, returns error. Compares
current.versionwithexpected_version. If mismatch, returnsStrataError::conflict. If match, increments version (Counter(n) -> Counter(n+1)), creates newStatewith updated value and timestamp, writes to storage. - Handler wrapping: The handler catches CAS errors and converts them to
Output::MaybeVersion(None)rather than propagating the error. Success returnsOutput::MaybeVersion(Some(new_version)).
Version increment rule: Counter(n).increment() -> Counter(n+1). The counter is strictly per-entity and increments by 1 on every successful write.
StateSet (unconditional write)
Client Handler Engine (StateCell) Transaction Storage
| | | | |
|-- StateSet ------->| | | |
| {branch,cell,val} | | | |
| |-- validate ------>| | |
| | | | |
| | |-- begin txn ------>| |
| | | | |
| | |-- txn.get -------->|-- read chain ---->|
| | | Key::new_state | |
| | | | |
| | | IF EXISTS: | |
| | | deserialize | |
| | | new_ver = | |
| | | cur.ver + 1 | |
| | | | |
| | | IF NOT EXISTS: | |
| | | new_ver = | |
| | | Counter(1) | |
| | | | |
| | | State { value, | |
| | | new_ver, now } | |
| | | | |
| | |-- txn.put -------->|-- write_set ----->|
| | | | |
| | |-- commit --------->|-- persist ------->|
| | | | |
| | |-- index update --->| |
| | | (if enabled) | |
| | | | |
|<-- Output::Version-|<- extract u64 ----|<- Versioned -------| |
| Counter(n+1) | | <Version> | |
Steps:
- Handler: Converts branch, validates cell name, calls
primitives.state.set(). ReturnsOutput::Version. - Engine (StateCell): Opens transaction. Reads current state. If exists, increments version counter. If not exists, starts at
Counter(1). Creates newState { value, version, updated_at }. Serializes and writes. Updates inverted index if enabled. - Unlike CAS:
setdoes not check the expected version - it always succeeds (barring OCC conflicts). It reads the current value only to determine the next version counter.
Returns: Output::Version(u64) - the new version counter after the write.
Storage Format
TypeTag: 0x03 (State)
Key format: Namespace::for_branch(branch_id) + TypeTag::State + cell_name_bytes
Value format: Value::String(JSON) containing State { value, version, updated_at }
Version in chain: Version::Txn(commit_version) from transaction coordinator
Version in struct: Version::Counter(n) per-entity counter
State Struct (stored as JSON)
State {
value: Value // The user's value
version: Version::Counter // Per-cell counter (1, 2, 3, ...)
updated_at: u64 // Timestamp in microseconds
}
Double versioning: State has two version numbers:
Version::Txn(commit_id)in the storageVersionChain- used for MVCC snapshot isolationVersion::Counter(n)inside theStatestruct - used for CAS semantics and returned to users
Transaction Behavior
| Aspect | Behavior |
|---|---|
| Isolation | Snapshot isolation |
| Concurrency control | OCC (single attempt, no retry) |
| CAS semantics | Application-level via version counter check in engine |
| Writes per operation | 1 (state cell) |
| Read-your-writes | Yes within transaction |
| Multi-command txn | Yes via Session |
Consistency Notes
- State is the only primitive with CAS (Compare-and-Swap) semantics via explicit version counter
- State wraps user values in a
State { value, version, updated_at }struct, unlike KV which stores values directly - The version counter is per-entity (per cell), unlike KV’s global transaction IDs or Event’s global sequence numbers
StateInitis idempotent - calling it on an existing cell returns the existing version without modifying the cellStateCaswithexpected_counter = Nonein the handler layer acts as init-if-not-exists, checking existence before callingstate.init()- The handler converts CAS failures to
Output::MaybeVersion(None)rather than returning an error, making it safe to use in optimistic loops - State has a
getvoperation for version history (not documented above as it’s not one of the main 4, but it usesdb.get_history()to retrieve all versions from theVersionChain)