Skip to content

Add ability to exclusively lock a record for performing a safe operation #483

Open
4 of 20 issues completed
Open

Description

@kriszyp

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.

Activity

  1. kriszyp commented on Jun 20, 2026

    @kriszyp
    MemberAuthor

    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.

  2. added theissue type on Aug 7, 2026
  3. self-assigned this
    on Sep 1, 2026
  4. 182 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Fields

Priority

P3

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions