2.2 KiB
2.2 KiB
UNDF: UNDF-2026-000000524
rocksdb-001: LockInfo.txn_ids linear scan — O(T²) shared-lock churn
Severity: HIGH
File
utilities/transactions/lock/point/point_lock_manager.cc
Defect
LockInfo::txn_ids is an autovector<TransactionID> (default 8 inline slots, grows
to heap). Three hot-path functions perform std::find() — O(T) linear scan — over
this vector, where T = number of concurrent transactions sharing a read lock on
a single key:
| Line | Function | Call site |
|---|---|---|
| 791 | PointLockManager::UnLockKey |
called in a loop over all keys held by the unlocking txn |
| 1513 | PerKeyPointLockManager::AcquireLocked |
shared-lock reentrant check |
| 1706 | PerKeyPointLockManager::UnLockKey |
called in a loop over all keys held by the unlocking txn |
Complexity
- Per-key unlock: O(T) scan — T concurrent readers per key
- Per-transaction unlock: O(K × T) — K keys in the transaction, T readers per key
- Under high shared-lock concurrency (OLAP, batch reads, read-committed workloads): T can reach hundreds; K can be thousands per transaction
- Overall unlock path becomes O(K × T) = O(N²) in the worst case
Root cause
struct LockInfo {
autovector<TransactionID> txn_ids; // ← vector, not set
...
};
// In UnLockKey (both lock managers):
auto txn_it = std::find(txns.begin(), txns.end(), txn_id); // O(T)
// In PerKeyPointLockManager::AcquireLocked:
auto lock_it = std::find(lock_info.txn_ids.begin(),
lock_info.txn_ids.end(), my_txn_id); // O(T)
Fix
Replace autovector<TransactionID> txn_ids with
std::unordered_set<TransactionID> txn_ids.
find()becomes O(1) averagepush_back()→insert()erase(iterator)→erase(value)(the RemoveTransaction helper disappears)txn_ids[0]→*txn_ids.begin()(exclusive lock path already asserts size==1)- Iteration in FillWaitIds / deadlock detection remains O(T) — unchanged
Speedup
Benchmark (unit test): T=1000 concurrent readers, K=1 key unlock
- Slow path (autovector + std::find): O(T) = 1000 ops per unlock
- Fast path (unordered_set): O(1) = 1 op per unlock
- Speedup: ~1000x at T=1000