Skip to content

Commit af3f964

Browse files
committed
concurrency: make the lock table limit configurable
Previously, this was hardcoded to 10K. We now introduce a new cluster setting to change this, if needed. We go through all ranges to update the limit on all exisiting lock table's in response to the cluster setting change. Release note: None Epic: none
1 parent 54e9027 commit af3f964

File tree

7 files changed

+90
-56
lines changed

7 files changed

+90
-56
lines changed

pkg/kv/kvserver/concurrency/concurrency_control.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@ type LockManager interface {
271271
// ExportUnreplicatedLocks runs exporter on each held, unreplicated lock
272272
// in the given span until the exporter returns false.
273273
ExportUnreplicatedLocks(span roachpb.Span, exporter func(*roachpb.LockAcquisition) bool)
274+
275+
// SetMaxLockTableSize updates the lock table's maximum size limit. It may
276+
// be used to dynamically adjust the lock table's size after it has been
277+
// initialized.
278+
SetMaxLockTableSize(maxLocks int64)
274279
}
275280

276281
// TransactionManager is concerned with tracking transactions that have their
@@ -354,10 +359,6 @@ type TestingAccessor interface {
354359

355360
// TestingTxnWaitQueue returns the concurrency manager's txnWaitQueue.
356361
TestingTxnWaitQueue() *txnwait.Queue
357-
358-
// TestingSetMaxLocks updates the locktable's lock limit. This can be used to
359-
// force the locktable to exceed its limit and clear locks.
360-
TestingSetMaxLocks(n int64)
361362
}
362363

363364
///////////////////////////////////
@@ -789,9 +790,10 @@ type lockTable interface {
789790
// String returns a debug string representing the state of the lockTable.
790791
String() string
791792

792-
// TestingSetMaxLocks updates the locktable's lock limit. This can be used to
793-
// force the locktable to exceed its limit and clear locks.
794-
TestingSetMaxLocks(maxLocks int64)
793+
// SetMaxLockTableSize updates the lock table's maximum size limit. It may
794+
// be used to dynamically adjust the lock table's size after it has been
795+
// initialized.
796+
SetMaxLockTableSize(maxLocks int64)
795797
}
796798

797799
// lockTableGuard is a handle to a request as it waits on conflicting locks in a

pkg/kv/kvserver/concurrency/concurrency_manager.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ var DiscoveredLocksThresholdToConsultTxnStatusCache = settings.RegisterIntSettin
103103
settings.NonNegativeInt,
104104
)
105105

106+
// DefaultLockTableSize controls the default upper bound on the number of locks
107+
// in a lock table.
108+
var DefaultLockTableSize = settings.RegisterIntSetting(
109+
settings.SystemOnly,
110+
"kv.lock_table.default_size",
111+
"the default upper bound on the number of locks in a lock table. This setting "+
112+
"controls the maximum number of locks that can be held in memory by the lock table "+
113+
"before it starts evicting locks to manage memory pressure.",
114+
10000,
115+
)
116+
106117
// BatchPushedLockResolution controls whether the lock table should allow
107118
// non-locking readers to defer and batch the resolution of conflicting locks
108119
// whose holder is known to be pending and have been pushed above the reader's
@@ -199,7 +210,7 @@ type Config struct {
199210

200211
func (c *Config) initDefaults() {
201212
if c.MaxLockTableSize == 0 {
202-
c.MaxLockTableSize = defaultLockTableSize
213+
c.MaxLockTableSize = DefaultLockTableSize.Get(&c.Settings.SV)
203214
}
204215
}
205216

@@ -759,9 +770,9 @@ func (m *managerImpl) TestingTxnWaitQueue() *txnwait.Queue {
759770
return m.twq.(*txnwait.Queue)
760771
}
761772

762-
// TestingSetMaxLocks implements the TestingAccessor interface.
763-
func (m *managerImpl) TestingSetMaxLocks(maxLocks int64) {
764-
m.lt.TestingSetMaxLocks(maxLocks)
773+
// SetMaxLockTableSize implements the LockManager interface.
774+
func (m *managerImpl) SetMaxLockTableSize(maxLocks int64) {
775+
m.lt.SetMaxLockTableSize(maxLocks)
765776
}
766777

767778
func (r *Request) isSingle(m kvpb.Method) bool {

pkg/kv/kvserver/concurrency/concurrency_manager_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ func TestConcurrencyManagerBasic(t *testing.T) {
641641
case "debug-set-max-locks":
642642
var n int
643643
d.ScanArgs(t, "n", &n)
644-
m.TestingSetMaxLocks(int64(n))
644+
m.SetMaxLockTableSize(int64(n))
645645
return ""
646646

647647
case "reset":

pkg/kv/kvserver/concurrency/lock_table.go

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ import (
3030
"github.com/cockroachdb/redact"
3131
)
3232

33-
// Default upper bound on the number of locks in a lockTable.
34-
const defaultLockTableSize = 10000
35-
3633
// The kind of waiting that the request is subject to.
3734
type waitKind int
3835

@@ -182,10 +179,6 @@ type treeMu struct {
182179
// primarily used for constraining memory consumption. Ideally, we should be
183180
// doing better memory accounting than this.
184181
numKeysLocked atomic.Int64
185-
186-
// For dampening the frequency with which we enforce
187-
// lockTableImpl.maxKeysLocked.
188-
lockAddMaxLocksCheckInterval uint64
189182
}
190183

191184
// lockTableImpl is an implementation of lockTable.
@@ -266,15 +259,21 @@ type lockTableImpl struct {
266259
// table. Locks on both Global and Local keys are stored in the same btree.
267260
locks treeMu
268261

269-
// maxKeysLocked is a soft maximum on amount of per-key lock information
270-
// tracking[1]. When it is exceeded, and subject to the dampening in
271-
// lockAddMaxLocksCheckInterval, locks will be cleared.
272-
//
273-
// [1] Simply put, the number of keyLocks objects in the lockTable btree.
274-
maxKeysLocked int64
275-
// When maxKeysLocked is exceeded, will attempt to clear down to minKeysLocked,
276-
// instead of clearing everything.
277-
minKeysLocked int64
262+
// lockTableLimitsMu contains the lock table limit fields protected by a mutex.
263+
lockTableLimitsMu struct {
264+
syncutil.Mutex
265+
// maxKeysLocked is a soft maximum on amount of per-key lock information
266+
// tracking[1]. When it is exceeded, and subject to the dampening in
267+
// lockAddMaxLocksCheckInterval, locks will be cleared.
268+
//
269+
// [1] Simply put, the number of keyLocks objects in the lockTable btree.
270+
maxKeysLocked int64
271+
// When maxKeysLocked is exceeded, will attempt to clear down to minKeysLocked,
272+
// instead of clearing everything.
273+
minKeysLocked int64
274+
// For dampening the frequency with which we enforce maxKeysLocked.
275+
lockAddMaxLocksCheckInterval uint64
276+
}
278277

279278
// txnStatusCache is a small LRU cache that tracks the status of
280279
// transactions that have been successfully pushed.
@@ -321,14 +320,27 @@ func newLockTable(
321320
}
322321

323322
func (t *lockTableImpl) setMaxKeysLocked(maxKeysLocked int64) {
323+
t.lockTableLimitsMu.Lock()
324+
defer t.lockTableLimitsMu.Unlock()
325+
324326
// Check at 5% intervals of the max count.
325327
lockAddMaxLocksCheckInterval := maxKeysLocked / int64(20)
326328
if lockAddMaxLocksCheckInterval == 0 {
327329
lockAddMaxLocksCheckInterval = 1
328330
}
329-
t.maxKeysLocked = maxKeysLocked
330-
t.minKeysLocked = maxKeysLocked / 2
331-
t.locks.lockAddMaxLocksCheckInterval = uint64(lockAddMaxLocksCheckInterval)
331+
t.lockTableLimitsMu.maxKeysLocked = maxKeysLocked
332+
t.lockTableLimitsMu.minKeysLocked = maxKeysLocked / 2
333+
t.lockTableLimitsMu.lockAddMaxLocksCheckInterval = uint64(lockAddMaxLocksCheckInterval)
334+
}
335+
336+
// shouldCheckMaxLocks returns true if allocating the supplied sequence number
337+
// implies that we should check whether the lock table has exceeded its max
338+
// locks limit or not. We dampen how often the check is performed.
339+
func (t *lockTableImpl) shouldCheckMaxLocks(seqNum uint64) bool {
340+
t.lockTableLimitsMu.Lock()
341+
defer t.lockTableLimitsMu.Unlock()
342+
interval := t.lockTableLimitsMu.lockAddMaxLocksCheckInterval
343+
return seqNum%interval == 0
332344
}
333345

334346
// lockTableGuardImpl is an implementation of lockTableGuard.
@@ -4161,10 +4173,9 @@ func (t *treeMu) Reset() {
41614173
t.btree.Reset()
41624174
}
41634175

4164-
func (t *treeMu) nextLockSeqNum() (seqNum uint64, checkMaxLocks bool) {
4176+
func (t *treeMu) nextLockSeqNum() uint64 {
41654177
t.lockIDSeqNum++
4166-
checkMaxLocks = t.lockIDSeqNum%t.lockAddMaxLocksCheckInterval == 0
4167-
return t.lockIDSeqNum, checkMaxLocks
4178+
return t.lockIDSeqNum
41684179
}
41694180

41704181
func (t *lockTableImpl) ScanOptimistic(req Request) lockTableGuard {
@@ -4352,10 +4363,9 @@ func (t *lockTableImpl) AddDiscoveredLock(
43524363
t.locks.mu.Lock()
43534364
iter := t.locks.MakeIter()
43544365
iter.FirstOverlap(&keyLocks{key: key})
4355-
checkMaxLocks := false
4366+
var lockSeqNum uint64
43564367
if !iter.Valid() {
4357-
var lockSeqNum uint64
4358-
lockSeqNum, checkMaxLocks = t.locks.nextLockSeqNum()
4368+
lockSeqNum = t.locks.nextLockSeqNum()
43594369
l = &keyLocks{id: lockSeqNum, key: key}
43604370
l.queuedLockingRequests.Init()
43614371
l.waitingReaders.Init()
@@ -4379,7 +4389,7 @@ func (t *lockTableImpl) AddDiscoveredLock(
43794389
// Can't release tree.mu until call l.discoveredLock() since someone may
43804390
// find an empty lock and remove it from the tree.
43814391
t.locks.mu.Unlock()
4382-
if checkMaxLocks {
4392+
if t.shouldCheckMaxLocks(lockSeqNum) {
43834393
t.checkMaxKeysLockedAndTryClear()
43844394
}
43854395
if err != nil {
@@ -4416,7 +4426,7 @@ func (t *lockTableImpl) AcquireLock(acq *roachpb.LockAcquisition) error {
44164426
// tree.mu.RLock().
44174427
iter := t.locks.MakeIter()
44184428
iter.FirstOverlap(&keyLocks{key: acq.Key})
4419-
checkMaxLocks := false
4429+
var lockSeqNum uint64
44204430
if !iter.Valid() {
44214431
if acq.Durability == lock.Replicated {
44224432
// Don't remember uncontended replicated locks. The downside is that
@@ -4428,8 +4438,7 @@ func (t *lockTableImpl) AcquireLock(acq *roachpb.LockAcquisition) error {
44284438
t.locks.mu.Unlock()
44294439
return nil
44304440
}
4431-
var lockSeqNum uint64
4432-
lockSeqNum, checkMaxLocks = t.locks.nextLockSeqNum()
4441+
lockSeqNum = t.locks.nextLockSeqNum()
44334442
kl = &keyLocks{id: lockSeqNum, key: acq.Key}
44344443
kl.queuedLockingRequests.Init()
44354444
kl.waitingReaders.Init()
@@ -4462,7 +4471,7 @@ func (t *lockTableImpl) AcquireLock(acq *roachpb.LockAcquisition) error {
44624471
err := kl.acquireLock(acq, t.clock, t.settings)
44634472
t.locks.mu.Unlock()
44644473

4465-
if checkMaxLocks {
4474+
if t.shouldCheckMaxLocks(lockSeqNum) {
44664475
t.checkMaxKeysLockedAndTryClear()
44674476
}
44684477
if err != nil {
@@ -4506,9 +4515,12 @@ func (t *lockTableImpl) MarkIneligibleForExport(acq *roachpb.LockAcquisition) er
45064515
// method relieves memory pressure by clearing as much per-key tracking as it
45074516
// can to bring things under budget.
45084517
func (t *lockTableImpl) checkMaxKeysLockedAndTryClear() {
4518+
t.lockTableLimitsMu.Lock()
4519+
defer t.lockTableLimitsMu.Unlock()
4520+
45094521
totalLocks := t.locks.numKeysLocked.Load()
4510-
if totalLocks > t.maxKeysLocked {
4511-
numToClear := totalLocks - t.minKeysLocked
4522+
if totalLocks > t.lockTableLimitsMu.maxKeysLocked {
4523+
numToClear := totalLocks - t.lockTableLimitsMu.minKeysLocked
45124524
numCleared := t.tryClearLocks(false /* force */, int(numToClear))
45134525
// Update metrics if we successfully cleared any number of locks.
45144526
if numCleared != 0 {
@@ -4948,8 +4960,8 @@ func (t *lockTableImpl) stringRLocked() string {
49484960
return sb.String()
49494961
}
49504962

4951-
// TestingSetMaxLocks implements the lockTable interface.
4952-
func (t *lockTableImpl) TestingSetMaxLocks(maxKeysLocked int64) {
4963+
// SetMaxLockTableSize implements the lockTable interface.
4964+
func (t *lockTableImpl) SetMaxLockTableSize(maxKeysLocked int64) {
49534965
t.setMaxKeysLocked(maxKeysLocked)
49544966
}
49554967

pkg/kv/kvserver/concurrency/lock_table_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ func TestLockTableBasic(t *testing.T) {
203203
)
204204
ltImpl.enabled = true
205205
ltImpl.enabledSeq = 1
206-
ltImpl.minKeysLocked = 0
206+
ltImpl.lockTableLimitsMu.minKeysLocked = 0
207207
lt = maybeWrapInVerifyingLockTable(ltImpl)
208208
txnsByName = make(map[string]*enginepb.TxnMeta)
209209
txnCounter = uint128.FromInts(0, 0)
@@ -895,7 +895,7 @@ func TestLockTableMaxLocks(t *testing.T) {
895895
5, roachpb.RangeID(3), hlc.NewClockForTesting(nil), cluster.MakeTestingClusterSettings(),
896896
m.LocksShedDueToMemoryLimit, m.NumLockShedDueToMemoryLimitEvents,
897897
)
898-
lt.minKeysLocked = 0
898+
lt.lockTableLimitsMu.minKeysLocked = 0
899899
lt.enabled = true
900900
var keys []roachpb.Key
901901
var guards []lockTableGuard
@@ -990,7 +990,7 @@ func TestLockTableMaxLocks(t *testing.T) {
990990
lt.Dequeue(guards[4])
991991
lt.Dequeue(guards[5])
992992
// Bump up the enforcement interval manually.
993-
lt.locks.lockAddMaxLocksCheckInterval = 2
993+
lt.lockTableLimitsMu.lockAddMaxLocksCheckInterval = 2
994994
// Add another discovered lock.
995995
added, err = lt.AddDiscoveredLock(
996996
newLock(&txnMeta, keys[9*20+12], lock.Intent),
@@ -1015,8 +1015,8 @@ func TestLockTableMaxLocks(t *testing.T) {
10151015
require.Equal(t, int64(100), m.LocksShedDueToMemoryLimit.Count())
10161016
require.Equal(t, int64(69), m.NumLockShedDueToMemoryLimitEvents.Count())
10171017
// Bump down the enforcement interval manually, and bump up minKeysLocked.
1018-
lt.locks.lockAddMaxLocksCheckInterval = 1
1019-
lt.minKeysLocked = 2
1018+
lt.lockTableLimitsMu.lockAddMaxLocksCheckInterval = 1
1019+
lt.lockTableLimitsMu.minKeysLocked = 2
10201020
// Three more guards dequeued. Now only 1 lock is notRemovable.
10211021
lt.Dequeue(guards[6])
10221022
lt.Dequeue(guards[7])
@@ -1046,7 +1046,7 @@ func TestLockTableMaxLocks(t *testing.T) {
10461046
require.Equal(t, int64(104), m.LocksShedDueToMemoryLimit.Count())
10471047
require.Equal(t, int64(70), m.NumLockShedDueToMemoryLimitEvents.Count())
10481048
// Restore minKeysLocked to 0.
1049-
lt.minKeysLocked = 0
1049+
lt.lockTableLimitsMu.minKeysLocked = 0
10501050
// Add locks to push us over 5 locks.
10511051
for i := 16; i < 20; i++ {
10521052
added, err = lt.AddDiscoveredLock(
@@ -1071,7 +1071,7 @@ func TestLockTableMaxLocksWithMultipleNotRemovableRefs(t *testing.T) {
10711071
2, roachpb.RangeID(3), hlc.NewClockForTesting(nil), cluster.MakeTestingClusterSettings(),
10721072
m.LocksShedDueToMemoryLimit, m.NumLockShedDueToMemoryLimitEvents,
10731073
)
1074-
lt.minKeysLocked = 0
1074+
lt.lockTableLimitsMu.minKeysLocked = 0
10751075
lt.enabled = true
10761076
var keys []roachpb.Key
10771077
var guards []lockTableGuard

pkg/kv/kvserver/concurrency/verifiable_lock_table.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (v verifyingLockTable) String() string {
133133
return v.lt.String()
134134
}
135135

136-
// TestingSetMaxLocks implements the lockTable interface.
137-
func (v verifyingLockTable) TestingSetMaxLocks(maxKeysLocked int64) {
138-
v.lt.TestingSetMaxLocks(maxKeysLocked)
136+
// SetMaxLockTableSize implements the lockTable interface.
137+
func (v verifyingLockTable) SetMaxLockTableSize(maxKeysLocked int64) {
138+
v.lt.SetMaxLockTableSize(maxKeysLocked)
139139
}

pkg/kv/kvserver/store.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/ctpb"
3838
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/policyrefresher"
3939
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/sidetransport"
40+
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/concurrency"
4041
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/idalloc"
4142
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/intentresolver"
4243
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvadmission"
@@ -1743,6 +1744,14 @@ func NewStore(
17431744
queueAdditionOnSystemConfigUpdateBurst.SetOnChange(&cfg.Settings.SV,
17441745
updateSystemConfigUpdateQueueLimits)
17451746

1747+
concurrency.DefaultLockTableSize.SetOnChange(&cfg.Settings.SV, func(ctx context.Context) {
1748+
newSize := concurrency.DefaultLockTableSize.Get(&cfg.Settings.SV)
1749+
s.VisitReplicas(func(repl *Replica) bool {
1750+
repl.concMgr.SetMaxLockTableSize(newSize)
1751+
return true // continue
1752+
})
1753+
})
1754+
17461755
if s.cfg.Gossip != nil {
17471756
s.storeGossip = NewStoreGossip(
17481757
cfg.Gossip,

0 commit comments

Comments
 (0)