You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add ability to exclusively lock a record for performing a safe operation #483
This will be another method on the Table/Resource interface, with the form of:
table.lock(primaryKey): Promise<WritableRecord>
(this is very similar interface to the update method, and returns the same record type).
If a node already has an exclusive lock on a record, it can immediately proceed.
If a node does not have an exclusive lock, but has a shared lock, it needs to broadcast a request to all nodes that it wants to acquire an exclusive lock. Once all other nodes have responded and granted this request, the operation can proceed. If an exclusive lock is held by one other node, the lock can be requested from that node.
We may support local-only locks for some use cases as well.
There may need to be a distinction between a node’s lock ownership (is retained after commit) and the transaction’s lock ownership (released after commit).
Changing a record from locked to unlocked should trigger a version change (but possibly only an audit entry for when a node is locking), to ensure no concurrent attempts to change.
Locking will be recorded in record metadata, and should delay any other writes that are attempted on that record.
Cross-referencing exploratory QA that motivates this primitive — the absence of a safe non-commutative claim/CAS shows up concretely across single-node and cluster:
Single-node safe-write matrix. Atomic deltas (addTo/subtractFrom) are the only safe concurrent-write primitive — the committed aggregate is exact on both engines because the merge is commit-resolved. Every CAS-shaped idiom leaks under contention because the decision is a snapshot read taken before commit and not re-validated at commit:
ifVersion / version-guarded read-modify-write
create-only first-write-wins
naive read-modify-write
REST If-Match (silently ignored)
(LMDB leaks on essentially every contended round; RocksDB more rarely.)
Consequences customers hit:
addTo returns void and the read-back is unreliable, so there's no "read your serialized position" → no exactly-once claim or hard limit.
subtractFrom has no floor → inventory oversells to negative under concurrency.
Cluster (2-node): a rate limiter built on addTo + read-then-decide over-admits by ~+7 on a 30-request burst with a limit of 10 during the replication-lag window — the count still converges (deltas merge), but each node's allow/deny decision is made on its local, not-yet-converged view. There is no conditional-write / lock anywhere, so a hard global limit (rate limit, inventory floor, exactly-once claim) is not achievable today.
table.lock() as proposed here would close all of these: acquire → read → decide → write → release. For simple single-key guards a lighter commit-time CAS / clamped-decrement would also suffice and avoid the cross-node round-trip, but the lock subsumes those cases.
Repro tests exist for each of the above (single-node concurrency matrix + the 2-node rate-limiter over-admission) if useful for validating a fix.
Activity
kriszyp commentedon Jun 20, 2026
Cross-referencing exploratory QA that motivates this primitive — the absence of a safe non-commutative claim/CAS shows up concretely across single-node and cluster:
Single-node safe-write matrix. Atomic deltas (
addTo/subtractFrom) are the only safe concurrent-write primitive — the committed aggregate is exact on both engines because the merge is commit-resolved. Every CAS-shaped idiom leaks under contention because the decision is a snapshot read taken before commit and not re-validated at commit:ifVersion/ version-guarded read-modify-writeIf-Match(silently ignored)(LMDB leaks on essentially every contended round; RocksDB more rarely.)
Consequences customers hit:
addToreturns void and the read-back is unreliable, so there's no "read your serialized position" → no exactly-once claim or hard limit.subtractFromhas no floor → inventory oversells to negative under concurrency.addTo+ read-then-decide over-admits by ~+7 on a 30-request burst with a limit of 10 during the replication-lag window — the count still converges (deltas merge), but each node's allow/deny decision is made on its local, not-yet-converged view. There is no conditional-write / lock anywhere, so a hard global limit (rate limit, inventory floor, exactly-once claim) is not achievable today.table.lock()as proposed here would close all of these: acquire → read → decide → write → release. For simple single-key guards a lighter commit-time CAS / clamped-decrement would also suffice and avoid the cross-node round-trip, but the lock subsumes those cases.Repro tests exist for each of the above (single-node concurrency matrix + the 2-node rate-limiter over-admission) if useful for validating a fix.
Record locks: regression test for an overrun holder re-staged past an…
Format unitTests/resources/query-array-scoping.test.js (inherited fro…
Record lock tests: compare against the lease deadline instead of elap…
Cluster record locks: table.lock() across nodes via replicated contro…
Cluster record locks: fix three defects the pre-push review found
Cluster record locks: keep two Phase 0 behaviors the cluster changes …
Cluster record locks: write the identity separator as an escape, not …
Cluster record locks: fix two majors the domain review adjudicated
Cluster record locks: fix three majors the graded review found in the…
Cluster record locks: clamp the hold upgrade on the monotonic deadlin…
182 remaining items
Cluster record locks: operator-agreed home map transport and successo…