-
Notifications
You must be signed in to change notification settings - Fork 4.1k
kv: implement Update lock strength for unreplicated locks #49684
Description
The lockTable currently only supports the exclusive lock strength, but was designed to support varying degrees of lock strengths. Specifically, we'd eventually like to add support for Shared and Upgrade locks as well.
cockroach/pkg/kv/kvserver/concurrency/lock/locking.proto
Lines 40 to 50 in 9e1666b
| // +-----------+-----------+-----------+-----------+-----------+ | |
| // | | None | Shared | Upgrade | Exclusive | | |
| // +-----------+-----------+-----------+-----------+-----------+ | |
| // | None | | | | X^† | | |
| // +-----------+-----------+-----------+-----------+-----------+ | |
| // | Shared | | | X | X | | |
| // +-----------+-----------+-----------+-----------+-----------+ | |
| // | Upgrade | | X | X | X | | |
| // +-----------+-----------+-----------+-----------+-----------+ | |
| // | Exclusive | X^† | X | X | X | | |
| // +-----------+-----------+-----------+-----------+-----------+ |
We currently have no need for Shared locks, but Upgrade locks would be immediately useful for unreplicated locks acquired during the initial row-fetch of an UPDATE statement (implicit SFU) or by a SELECT ... FOR UPDATE statement (explicit SFU).
cockroach/pkg/kv/kvserver/concurrency/lock/locking.proto
Lines 90 to 130 in 9e1666b
| // Upgrade (U) locks are a hybrid of Shared and Exclusive locks which are | |
| // used to prevent a common form of deadlock. When a transaction intends to | |
| // modify existing KVs, it is often the case that it reads the KVs first and | |
| // then attempts to modify them. Under pessimistic concurrency control, this | |
| // would correspond to first acquiring a Shared lock on the keys and then | |
| // converting the lock to an Exclusive lock when modifying the keys. If two | |
| // transactions were to acquire the Shared lock initially and then attempt | |
| // to update the keys concurrently, both transactions would get stuck | |
| // waiting for the other to release its Shared lock and a deadlock would | |
| // occur. To resolve the deadlock, one of the two transactions would need to | |
| // be aborted. | |
| // | |
| // To avoid this potential deadlock problem, an Upgrade lock can be used in | |
| // place of a Shared lock. Upgrade locks are not compatible with any other | |
| // form of locking. As with Shared locks, the lock holder of a Shared lock | |
| // on a key is only allowed to read from the key while the lock is held. | |
| // This resolves the deadlock scenario presented above because only one of | |
| // the transactions would have been able to acquire an Upgrade lock at a | |
| // time while reading the initial state of the KVs. This means that the | |
| // Shared-to-Exclusive lock upgrade would never need to wait on another | |
| // transaction to release its locks. | |
| // | |
| // Under pure pessimistic concurrency control, an Upgrade lock is equivalent | |
| // to an Exclusive lock. However, unlike with Exclusive locks, reads under | |
| // optimistic concurrency control do not conflict with Upgrade locks. This | |
| // is because a transaction can only hold an Upgrade lock on keys that it | |
| // has not yet modified. This improves concurrency between read and write | |
| // transactions compared to if the writing transaction had immediately | |
| // acquired an Exclusive lock. | |
| // | |
| // The trade-off here is twofold. First, if the Upgrade lock holder does | |
| // convert its lock on a key to an Exclusive lock after an optimistic read | |
| // has observed the state of the key, the transaction that performed the | |
| // optimistic read may be unable to perform a successful read refresh if it | |
| // attempts to refresh to a timestamp at or past the timestamp of the lock | |
| // conversion. Second, the optimistic reads permitted while the Upgrade lock | |
| // is held will bump the timestamp cache. This may result in the Upgrade | |
| // lock holder being forced to increase its write timestamp when converting | |
| // to an Exclusive lock, which in turn may force it to restart if its read | |
| // refresh fails. | |
| Upgrade = 2; |
The major benefit of this form of lock is that it does not conflict with non-locking reads. This avoids blocking these reads, at the expense of undermining some of the protection against transactions retries that is provided by SFU. This is because these non-locking reads are allowed to slip underneath Upgrade locks and bump the timestamp cache, which may force the UPDATE to have to push its timestamp when it returns to write an intent. This is probably ok, and will certainly be once we also support Shared locks and SELECT ... FOR SHARE so that users can have full control over row-level locking.
This should improve throughput on YCSB-B (95% reads, 5% updates). It's unclear how it will affect YCSB-A (50% reads, 50% updates).
Jira issue: CRDB-4209