Skip to content

Commit f4a2a01

Browse files
authored
Force oldest locks into shard locks due to range limits (#11329)
1 parent 67cc21e commit f4a2a01

File tree

3 files changed

+157
-39
lines changed

3 files changed

+157
-39
lines changed

ydb/core/client/locks_ut.cpp

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,8 +1810,14 @@ static void LocksLimit() {
18101810

18111811
using TLock = TSysTables::TLocksTable::TLock;
18121812

1813-
ui32 limit = NDataShard::TLockLocker::LockLimit();
1814-
const ui32 factor = 100;
1813+
auto prevLimit = NDataShard::TLockLocker::LockLimit();
1814+
NDataShard::TLockLocker::SetLockLimit(20);
1815+
Y_DEFER {
1816+
NDataShard::TLockLocker::SetLockLimit(prevLimit);
1817+
};
1818+
1819+
const ui32 limit = NDataShard::TLockLocker::LockLimit();
1820+
const ui32 factor = 5;
18151821

18161822
const char * query = R"((
18171823
(let row0_ '('('key (Uint32 '%u))))
@@ -1916,9 +1922,13 @@ static void ShardLocks() {
19161922
NKikimrMiniKQL::TResult res;
19171923
TClient::TFlatQueryOptions opts;
19181924

1925+
auto prevLimit = NDataShard::TLockLocker::TotalRangesLimit();
1926+
NDataShard::TLockLocker::SetTotalRangesLimit(10);
1927+
Y_DEFER {
1928+
NDataShard::TLockLocker::SetTotalRangesLimit(prevLimit);
1929+
};
19191930

1920-
ui32 limit = NDataShard::TLockLocker::LockLimit();
1921-
//const ui32 factor = 100;
1931+
const ui32 limit = NDataShard::TLockLocker::TotalRangesLimit();
19221932

19231933
const char * setLock = R"___((
19241934
(let range_ '('IncFrom 'IncTo '('key (Uint32 '%u) (Uint32 '%u))))
@@ -1932,14 +1942,16 @@ static void ShardLocks() {
19321942
// Attach lots of ranges to a single lock.
19331943
TVector<NMiniKQL::IEngineFlat::TTxLock> locks;
19341944
ui64 lockId = 0;
1935-
for (ui32 i = 0; i < limit + 1; ++i) {
1945+
for (ui32 i = 0; i < limit; ++i) {
1946+
Cout << "... reading range " << i << Endl;
19361947
cs.Client.FlatQuery(Sprintf(setLock, i * 10, i * 10 + 5, lockId), res);
19371948
ExtractResultLocks<TLocksVer>(res, locks);
19381949
lockId = locks.back().LockId;
19391950
}
19401951

1941-
// We now have too many rnages attached to locks and new lock
1942-
// will be forced to be shard lock.
1952+
// We now have too many ranges attached to locks and the oldest lock
1953+
// will be forced to be a shard lock.
1954+
Cout << "... reading additional range with a new lock" << Endl;
19431955
cs.Client.FlatQuery(Sprintf(setLock, 0, 5, 0), res);
19441956
ExtractResultLocks<TLocksVer>(res, locks);
19451957

@@ -1953,6 +1965,7 @@ static void ShardLocks() {
19531965
))
19541966
))___";
19551967
{
1968+
Cout << "... checking the last lock (must be set)" << Endl;
19561969
cs.Client.FlatQuery(Sprintf(checkLock,
19571970
TLocksVer::TableName(),
19581971
TLocksVer::Key(locks.back().LockId,
@@ -1969,9 +1982,11 @@ static void ShardLocks() {
19691982
UNIT_ASSERT_VALUES_EQUAL(lock.Counter, locks.back().Counter);
19701983
}
19711984

1972-
// Break locks by single row update.
1985+
// Upsert key 48, which does not conflict with either lock.
1986+
// However since the first lock is forced to be a shard lock it will break.
1987+
Cout << "... upserting key 48 (will break the first lock despite no conflicts)" << Endl;
19731988
const char * lockUpdate = R"___((
1974-
(let row0_ '('('key (Uint32 '42))))
1989+
(let row0_ '('('key (Uint32 '48))))
19751990
(let update_ '('('value (Uint32 '0))))
19761991
(let ret_ (AsList
19771992
(UpdateRow '/dc-1/Dir/A row0_ update_)
@@ -1980,8 +1995,9 @@ static void ShardLocks() {
19801995
))___";
19811996
cs.Client.FlatQuery(lockUpdate, opts, res);
19821997

1983-
// Check locks are broken.
1998+
// Check the last lock is not broken.
19841999
{
2000+
Cout << "... checking the last lock (must not be broken)" << Endl;
19852001
cs.Client.FlatQuery(Sprintf(checkLock,
19862002
TLocksVer::TableName(),
19872003
TLocksVer::Key(locks.back().LockId,
@@ -1991,10 +2007,16 @@ static void ShardLocks() {
19912007
TLocksVer::Columns()), res);
19922008
TValue result = TValue::Create(res.GetValue(), res.GetType());
19932009
TValue xres = result["Result"];
1994-
UNIT_ASSERT(!xres.HaveValue());
2010+
UNIT_ASSERT(xres.HaveValue());
2011+
auto lock = ExtractRowLock<TLocksVer>(xres);
2012+
UNIT_ASSERT_VALUES_EQUAL(lock.LockId, locks.back().LockId);
2013+
UNIT_ASSERT_VALUES_EQUAL(lock.Generation, locks.back().Generation);
2014+
UNIT_ASSERT_VALUES_EQUAL(lock.Counter, locks.back().Counter);
19952015
}
19962016

2017+
// Check the first lock is broken.
19972018
{
2019+
Cout << "... checking the first lock (must be broken)" << Endl;
19982020
cs.Client.FlatQuery(Sprintf(checkLock,
19992021
TLocksVer::TableName(),
20002022
TLocksVer::Key(locks[0].LockId,

ydb/core/tx/locks/locks.cpp

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -402,9 +402,60 @@ void TTableLocks::RemoveWriteLock(TLockInfo* lock) {
402402

403403
// TLockLocker
404404

405+
namespace {
406+
407+
static constexpr ui64 DefaultLockLimit() {
408+
// Valgrind and sanitizers are too slow
409+
// Some tests cannot exhaust default limit in under 5 minutes
410+
return NValgrind::PlainOrUnderValgrind(
411+
NSan::PlainOrUnderSanitizer(
412+
20000,
413+
1000),
414+
1000);
415+
}
416+
417+
static constexpr ui64 DefaultLockRangesLimit() {
418+
return 50000;
419+
}
420+
421+
static constexpr ui64 DefaultTotalRangesLimit() {
422+
return 1000000;
423+
}
424+
425+
static std::atomic<ui64> g_LockLimit{ DefaultLockLimit() };
426+
static std::atomic<ui64> g_LockRangesLimit{ DefaultLockRangesLimit() };
427+
static std::atomic<ui64> g_TotalRangesLimit{ DefaultTotalRangesLimit() };
428+
429+
} // namespace
430+
431+
ui64 TLockLocker::LockLimit() {
432+
return g_LockLimit.load(std::memory_order_relaxed);
433+
}
434+
435+
ui64 TLockLocker::LockRangesLimit() {
436+
return g_LockRangesLimit.load(std::memory_order_relaxed);
437+
}
438+
439+
ui64 TLockLocker::TotalRangesLimit() {
440+
return g_TotalRangesLimit.load(std::memory_order_relaxed);
441+
}
442+
443+
void TLockLocker::SetLockLimit(ui64 newLimit) {
444+
g_LockLimit.store(newLimit, std::memory_order_relaxed);
445+
}
446+
447+
void TLockLocker::SetLockRangesLimit(ui64 newLimit) {
448+
g_LockRangesLimit.store(newLimit, std::memory_order_relaxed);
449+
}
450+
451+
void TLockLocker::SetTotalRangesLimit(ui64 newLimit) {
452+
g_TotalRangesLimit.store(newLimit, std::memory_order_relaxed);
453+
}
454+
405455
void TLockLocker::AddPointLock(const TLockInfo::TPtr& lock, const TPointKey& key) {
406456
if (lock->AddPoint(key)) {
407457
key.Table->AddPointLock(key, lock.Get());
458+
LocksWithRanges.PushBack(lock.Get());
408459
} else {
409460
key.Table->AddShardLock(lock.Get());
410461
}
@@ -413,21 +464,27 @@ void TLockLocker::AddPointLock(const TLockInfo::TPtr& lock, const TPointKey& key
413464
void TLockLocker::AddRangeLock(const TLockInfo::TPtr& lock, const TRangeKey& key) {
414465
if (lock->AddRange(key)) {
415466
key.Table->AddRangeLock(key, lock.Get());
467+
LocksWithRanges.PushBack(lock.Get());
416468
} else {
417469
key.Table->AddShardLock(lock.Get());
418470
}
419471
}
420472

421-
void TLockLocker::AddShardLock(const TLockInfo::TPtr& lock, TIntrusiveList<TTableLocks, TTableLocksReadListTag>& readTables) {
473+
void TLockLocker::MakeShardLock(TLockInfo* lock) {
422474
if (!lock->IsShardLock()) {
423475
for (const TPathId& tableId : lock->GetReadTables()) {
424-
Tables.at(tableId)->RemoveRangeLock(lock.Get());
476+
Tables.at(tableId)->RemoveRangeLock(lock);
425477
}
426478
lock->MakeShardLock();
479+
LocksWithRanges.Remove(lock);
427480
for (const TPathId& tableId : lock->GetReadTables()) {
428-
Tables.at(tableId)->AddShardLock(lock.Get());
481+
Tables.at(tableId)->AddShardLock(lock);
429482
}
430483
}
484+
}
485+
486+
void TLockLocker::AddShardLock(const TLockInfo::TPtr& lock, TIntrusiveList<TTableLocks, TTableLocksReadListTag>& readTables) {
487+
MakeShardLock(lock.Get());
431488
for (auto& table : readTables) {
432489
const TPathId& tableId = table.GetTableId();
433490
Y_ABORT_UNLESS(Tables.at(tableId).Get() == &table);
@@ -519,6 +576,9 @@ void TLockLocker::RemoveBrokenRanges() {
519576
TLockInfo::TPtr TLockLocker::GetOrAddLock(ui64 lockId, ui32 lockNodeId) {
520577
auto it = Locks.find(lockId);
521578
if (it != Locks.end()) {
579+
if (it->second->IsInList<TLockInfoRangesListTag>()) {
580+
LocksWithRanges.PushBack(it->second.Get());
581+
}
522582
if (it->second->IsInList<TLockInfoExpireListTag>()) {
523583
ExpireQueue.PushBack(it->second.Get());
524584
}
@@ -591,6 +651,7 @@ void TLockLocker::RemoveOneLock(ui64 lockTxId, ILocksDb* db) {
591651
for (const TPathId& tableId : txLock->GetWriteTables()) {
592652
Tables.at(tableId)->RemoveWriteLock(txLock.Get());
593653
}
654+
LocksWithRanges.Remove(txLock.Get());
594655
txLock->CleanupConflicts();
595656
Locks.erase(it);
596657

@@ -634,6 +695,7 @@ void TLockLocker::RemoveSchema(const TPathId& tableId, ILocksDb* db) {
634695
Y_ABORT_UNLESS(Tables.empty());
635696
Locks.clear();
636697
ShardLocks.clear();
698+
LocksWithRanges.Clear();
637699
ExpireQueue.Clear();
638700
BrokenLocks.Clear();
639701
BrokenPersistentLocks.Clear();
@@ -643,21 +705,41 @@ void TLockLocker::RemoveSchema(const TPathId& tableId, ILocksDb* db) {
643705
PendingSubscribeLocks.clear();
644706
}
645707

646-
bool TLockLocker::ForceShardLock(const TPathId& tableId) const {
647-
auto it = Tables.find(tableId);
648-
if (it != Tables.end()) {
649-
if (it->second->RangeCount() > LockLimit()) {
650-
return true;
651-
}
708+
bool TLockLocker::ForceShardLock(
709+
const TLockInfo::TPtr& lock,
710+
const TIntrusiveList<TTableLocks, TTableLocksReadListTag>& readTables,
711+
ui64 newRanges)
712+
{
713+
if (lock->NumPoints() + lock->NumRanges() + newRanges > LockRangesLimit()) {
714+
// Lock has too many ranges, will never fit in
715+
return true;
652716
}
653-
return false;
654-
}
655717

656-
bool TLockLocker::ForceShardLock(const TIntrusiveList<TTableLocks, TTableLocksReadListTag>& readTables) const {
657718
for (auto& table : readTables) {
658-
if (table.RangeCount() > LockLimit())
659-
return true;
719+
while (table.RangeCount() + newRanges > TotalRangesLimit()) {
720+
if (LocksWithRanges.Empty()) {
721+
// Too many new ranges (e.g. TotalRangesLimit < LockRangesLimit)
722+
return true;
723+
}
724+
725+
// Try to reduce the number of ranges until new ranges fit in
726+
TLockInfo* next = LocksWithRanges.PopFront();
727+
if (next == lock.Get()) {
728+
bool wasLast = LocksWithRanges.Empty();
729+
LocksWithRanges.PushBack(next);
730+
if (wasLast) {
731+
return true;
732+
}
733+
// We want to handle the newest lock last
734+
continue;
735+
}
736+
737+
// Reduce the number of ranges by making the oldest lock into a shard lock
738+
MakeShardLock(next);
739+
Self->IncCounter(COUNTER_LOCKS_WHOLE_SHARD);
740+
}
660741
}
742+
661743
return false;
662744
}
663745

@@ -771,8 +853,6 @@ TVector<TSysLocks::TLock> TSysLocks::ApplyLocks() {
771853
return TVector<TLock>();
772854
}
773855

774-
bool shardLock = Locker.ForceShardLock(Update->ReadTables);
775-
776856
TLockInfo::TPtr lock;
777857
ui64 counter = TLock::ErrorNotSet;
778858

@@ -791,6 +871,12 @@ TVector<TSysLocks::TLock> TSysLocks::ApplyLocks() {
791871
} else if (lock->IsBroken()) {
792872
counter = TLock::ErrorBroken;
793873
} else {
874+
bool shardLock = (
875+
lock->IsShardLock() ||
876+
Locker.ForceShardLock(
877+
lock,
878+
Update->ReadTables,
879+
Update->PointLocks.size() + Update->RangeLocks.size()));
794880
if (shardLock) {
795881
Locker.AddShardLock(lock, Update->ReadTables);
796882
Self->IncCounter(COUNTER_LOCKS_WHOLE_SHARD);

ydb/core/tx/locks/locks.h

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ struct TLockInfoWriteConflictListTag {};
252252
struct TLockInfoBrokenListTag {};
253253
struct TLockInfoBrokenPersistentListTag {};
254254
struct TLockInfoExpireListTag {};
255+
struct TLockInfoRangesListTag {};
255256

256257
/// Aggregates shard, point and range locks
257258
class TLockInfo
@@ -263,6 +264,7 @@ class TLockInfo
263264
, public TIntrusiveListItem<TLockInfo, TLockInfoBrokenListTag>
264265
, public TIntrusiveListItem<TLockInfo, TLockInfoBrokenPersistentListTag>
265266
, public TIntrusiveListItem<TLockInfo, TLockInfoExpireListTag>
267+
, public TIntrusiveListItem<TLockInfo, TLockInfoRangesListTag>
266268
{
267269
friend class TTableLocks;
268270
friend class TLockLocker;
@@ -508,16 +510,19 @@ class TLockLocker {
508510
friend class TSysLocks;
509511

510512
public:
511-
/// Prevent unlimited lock's count growth
512-
static constexpr ui64 LockLimit() {
513-
// Valgrind and sanitizers are too slow
514-
// Some tests cannot exhaust default limit in under 5 minutes
515-
return NValgrind::PlainOrUnderValgrind(
516-
NSan::PlainOrUnderSanitizer(
517-
16 * 1024,
518-
1024),
519-
1024);
520-
}
513+
/// Prevent unlimited locks count growth
514+
static ui64 LockLimit();
515+
516+
/// Prevent unlimited range count growth
517+
static ui64 LockRangesLimit();
518+
519+
/// Prevent unlimited number of total ranges
520+
static ui64 TotalRangesLimit();
521+
522+
/// Make it possible to override defaults (e.g. for tests)
523+
static void SetLockLimit(ui64 newLimit);
524+
static void SetLockRangesLimit(ui64 newLimit);
525+
static void SetTotalRangesLimit(ui64 newLimit);
521526

522527
/// We don't expire locks until this time limit after they are created
523528
static constexpr TDuration LockTimeLimit() { return TDuration::Minutes(5); }
@@ -535,6 +540,7 @@ class TLockLocker {
535540

536541
void AddPointLock(const TLockInfo::TPtr& lock, const TPointKey& key);
537542
void AddRangeLock(const TLockInfo::TPtr& lock, const TRangeKey& key);
543+
void MakeShardLock(TLockInfo* lock);
538544
void AddShardLock(const TLockInfo::TPtr& lock, TIntrusiveList<TTableLocks, TTableLocksReadListTag>& readTables);
539545
void AddWriteLock(const TLockInfo::TPtr& lock, TIntrusiveList<TTableLocks, TTableLocksWriteListTag>& writeTables);
540546

@@ -592,8 +598,10 @@ class TLockLocker {
592598

593599
void UpdateSchema(const TPathId& tableId, const TVector<NScheme::TTypeInfo>& keyColumnTypes);
594600
void RemoveSchema(const TPathId& tableId, ILocksDb* db);
595-
bool ForceShardLock(const TPathId& tableId) const;
596-
bool ForceShardLock(const TIntrusiveList<TTableLocks, TTableLocksReadListTag>& readTables) const;
601+
bool ForceShardLock(
602+
const TLockInfo::TPtr& lock,
603+
const TIntrusiveList<TTableLocks,
604+
TTableLocksReadListTag>& readTables, ui64 newRanges);
597605

598606
void ScheduleBrokenLock(TLockInfo* lock);
599607
void ScheduleRemoveBrokenRanges(ui64 lockId, const TRowVersion& at);
@@ -633,6 +641,8 @@ class TLockLocker {
633641
THashMap<ui64, TLockInfo::TPtr> Locks; // key is LockId
634642
THashMap<TPathId, TTableLocks::TPtr> Tables;
635643
THashSet<ui64> ShardLocks;
644+
// A list of locks that have ranges (from oldest to newest)
645+
TIntrusiveList<TLockInfo, TLockInfoRangesListTag> LocksWithRanges;
636646
// A list of locks that may be removed when enough time passes
637647
TIntrusiveList<TLockInfo, TLockInfoExpireListTag> ExpireQueue;
638648
// A list of broken, but not yet removed locks

0 commit comments

Comments
 (0)