Skip to content

Commit 4993957

Browse files
committed
chore: preparation step for lock fingerprints
The main change here is introduction of the strong type LockTag that differentiates from a string_view key. Also, some testing improvements to improve the footprint of the next PR. Signed-off-by: Roman Gershman <[email protected]>
1 parent d99b0ed commit 4993957

18 files changed

+175
-157
lines changed

src/core/heap_size.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,14 @@ namespace dfly {
2929

3030
namespace heap_size_detail {
3131

32+
template <class, class = void> struct has_marked_stackonly : std::false_type {};
33+
34+
template <class T>
35+
struct has_marked_stackonly<T, std::void_t<typename T::is_stackonly>> : std::true_type {};
36+
3237
template <typename T> constexpr bool StackOnlyType() {
33-
return std::is_trivial_v<T> || std::is_same_v<T, std::string_view>;
38+
return std::is_trivial_v<T> || std::is_same_v<T, std::string_view> ||
39+
has_marked_stackonly<T>::value;
3440
}
3541

3642
template <typename T, typename = void> struct has_used_mem : std::false_type {};

src/server/cluster/cluster_config.cc

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,32 +55,8 @@ bool ClusterConfig::IsEmulated() {
5555
return cluster_mode == ClusterMode::kEmulatedCluster;
5656
}
5757

58-
string_view ClusterConfig::KeyTag(string_view key) {
59-
auto options = KeyLockArgs::GetLockTagOptions();
60-
61-
if (!absl::StartsWith(key, options.prefix)) {
62-
return key;
63-
}
64-
65-
const size_t start = key.find(options.open_locktag);
66-
if (start == key.npos) {
67-
return key;
68-
}
69-
70-
size_t end = start;
71-
for (unsigned i = 0; i <= options.skip_n_end_delimiters; ++i) {
72-
size_t next = end + 1;
73-
end = key.find(options.close_locktag, next);
74-
if (end == key.npos || end == next) {
75-
return key;
76-
}
77-
}
78-
79-
return key.substr(start + 1, end - start - 1);
80-
}
81-
8258
SlotId ClusterConfig::KeySlot(string_view key) {
83-
string_view tag = KeyTag(key);
59+
string_view tag = LockTagOptions::instance().Tag(key);
8460
return crc16(tag.data(), tag.length()) & kMaxSlotNum;
8561
}
8662

src/server/cluster/cluster_config.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,9 @@ class ClusterConfig {
6464
}
6565

6666
static bool IsShardedByTag() {
67-
return IsEnabledOrEmulated() || KeyLockArgs::GetLockTagOptions().enabled;
67+
return IsEnabledOrEmulated() || LockTagOptions::instance().enabled;
6868
}
6969

70-
// If the key contains the {...} pattern, return only the part between { and }
71-
static std::string_view KeyTag(std::string_view key);
72-
7370
// Returns an instance with `config` if it is valid.
7471
// Returns heap-allocated object as it is too big for a stack frame.
7572
static std::shared_ptr<ClusterConfig> CreateFromConfig(std::string_view my_id,

src/server/cluster/cluster_config_test.cc

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,63 +27,67 @@ class ClusterConfigTest : public BaseFamilyTest {
2727
const string kMyId = "my-id";
2828
};
2929

30+
inline string_view GetTag(string_view key) {
31+
return LockTagOptions::instance().Tag(key);
32+
}
33+
3034
TEST_F(ClusterConfigTest, KeyTagTest) {
3135
SetTestFlag("lock_on_hashtags", "true");
3236

33-
EXPECT_EQ(ClusterConfig::KeyTag("{user1000}.following"), "user1000");
37+
EXPECT_EQ(GetTag("{user1000}.following"), "user1000");
3438

35-
EXPECT_EQ(ClusterConfig::KeyTag("foo{{bar}}zap"), "{bar");
39+
EXPECT_EQ(GetTag("foo{{bar}}zap"), "{bar");
3640

37-
EXPECT_EQ(ClusterConfig::KeyTag("foo{bar}{zap}"), "bar");
41+
EXPECT_EQ(GetTag("foo{bar}{zap}"), "bar");
3842

3943
string_view key = " foo{}{bar}";
40-
EXPECT_EQ(key, ClusterConfig::KeyTag(key));
44+
EXPECT_EQ(key, GetTag(key));
4145

4246
key = "{}foo{bar}{zap}";
43-
EXPECT_EQ(key, ClusterConfig::KeyTag(key));
47+
EXPECT_EQ(key, GetTag(key));
4448

4549
SetTestFlag("locktag_delimiter", ":");
4650
TEST_InvalidateLockTagOptions();
4751

4852
key = "{user1000}.following";
49-
EXPECT_EQ(ClusterConfig::KeyTag(key), key);
53+
EXPECT_EQ(GetTag(key), key);
5054

51-
EXPECT_EQ(ClusterConfig::KeyTag("bull:queue1:123"), "queue1");
52-
EXPECT_EQ(ClusterConfig::KeyTag("bull:queue:1:123"), "queue");
53-
EXPECT_EQ(ClusterConfig::KeyTag("bull:queue:1:123:456:789:1000"), "queue");
55+
EXPECT_EQ(GetTag("bull:queue1:123"), "queue1");
56+
EXPECT_EQ(GetTag("bull:queue:1:123"), "queue");
57+
EXPECT_EQ(GetTag("bull:queue:1:123:456:789:1000"), "queue");
5458

5559
key = "bull::queue:1:123";
56-
EXPECT_EQ(ClusterConfig::KeyTag(key), key);
60+
EXPECT_EQ(GetTag(key), key);
5761

5862
SetTestFlag("locktag_delimiter", ":");
5963
SetTestFlag("locktag_skip_n_end_delimiters", "0");
6064
SetTestFlag("locktag_prefix", "bull");
6165
TEST_InvalidateLockTagOptions();
62-
EXPECT_EQ(ClusterConfig::KeyTag("bull:queue:123"), "queue");
63-
EXPECT_EQ(ClusterConfig::KeyTag("bull:queue:123:456:789:1000"), "queue");
66+
EXPECT_EQ(GetTag("bull:queue:123"), "queue");
67+
EXPECT_EQ(GetTag("bull:queue:123:456:789:1000"), "queue");
6468

6569
key = "not-bull:queue1:123";
66-
EXPECT_EQ(ClusterConfig::KeyTag(key), key);
70+
EXPECT_EQ(GetTag(key), key);
6771

6872
SetTestFlag("locktag_delimiter", ":");
6973
SetTestFlag("locktag_skip_n_end_delimiters", "1");
7074
SetTestFlag("locktag_prefix", "bull");
7175
TEST_InvalidateLockTagOptions();
7276

7377
key = "bull:queue1:123";
74-
EXPECT_EQ(ClusterConfig::KeyTag(key), key);
75-
EXPECT_EQ(ClusterConfig::KeyTag("bull:queue:1:123"), "queue:1");
76-
EXPECT_EQ(ClusterConfig::KeyTag("bull:queue:1:123:456:789:1000"), "queue:1");
78+
EXPECT_EQ(GetTag(key), key);
79+
EXPECT_EQ(GetTag("bull:queue:1:123"), "queue:1");
80+
EXPECT_EQ(GetTag("bull:queue:1:123:456:789:1000"), "queue:1");
7781

7882
key = "bull::queue:1:123";
79-
EXPECT_EQ(ClusterConfig::KeyTag(key), key);
83+
EXPECT_EQ(GetTag(key), key);
8084

8185
SetTestFlag("locktag_delimiter", "|");
8286
SetTestFlag("locktag_skip_n_end_delimiters", "2");
8387
SetTestFlag("locktag_prefix", "");
8488
TEST_InvalidateLockTagOptions();
8589

86-
EXPECT_EQ(ClusterConfig::KeyTag("|a|b|c|d|e"), "a|b|c");
90+
EXPECT_EQ(GetTag("|a|b|c|d|e"), "a|b|c");
8791
}
8892

8993
TEST_F(ClusterConfigTest, ConfigSetInvalidEmpty) {

src/server/common.cc

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ using namespace std;
5252
using namespace util;
5353

5454
namespace {
55+
5556
// Thread-local cache with static linkage.
5657
thread_local std::optional<LockTagOptions> locktag_lock_options;
58+
5759
} // namespace
5860

5961
void TEST_InvalidateLockTagOptions() {
@@ -63,7 +65,7 @@ void TEST_InvalidateLockTagOptions() {
6365
[](ShardId shard, ProactorBase* proactor) { locktag_lock_options = nullopt; });
6466
}
6567

66-
/* static */ LockTagOptions KeyLockArgs::GetLockTagOptions() {
68+
const LockTagOptions& LockTagOptions::instance() {
6769
if (!locktag_lock_options.has_value()) {
6870
string delimiter = absl::GetFlag(FLAGS_locktag_delimiter);
6971
if (delimiter.empty()) {
@@ -87,12 +89,26 @@ void TEST_InvalidateLockTagOptions() {
8789
return *locktag_lock_options;
8890
}
8991

90-
string_view KeyLockArgs::GetLockKey(string_view key) {
91-
if (GetLockTagOptions().enabled) {
92-
return ClusterConfig::KeyTag(key);
92+
std::string_view LockTagOptions::Tag(std::string_view key) const {
93+
if (!absl::StartsWith(key, prefix)) {
94+
return key;
95+
}
96+
97+
const size_t start = key.find(open_locktag);
98+
if (start == key.npos) {
99+
return key;
100+
}
101+
102+
size_t end = start;
103+
for (unsigned i = 0; i <= skip_n_end_delimiters; ++i) {
104+
size_t next = end + 1;
105+
end = key.find(close_locktag, next);
106+
if (end == key.npos || end == next) {
107+
return key;
108+
}
93109
}
94110

95-
return key;
111+
return key.substr(start + 1, end - start - 1);
96112
}
97113

98114
atomic_uint64_t used_mem_peak(0);
@@ -445,4 +461,15 @@ std::ostream& operator<<(std::ostream& os, ArgSlice list) {
445461
return os << "]";
446462
}
447463

464+
LockTag::LockTag(std::string_view key) {
465+
if (LockTagOptions::instance().enabled)
466+
str_ = LockTagOptions::instance().Tag(key);
467+
else
468+
str_ = key;
469+
}
470+
471+
LockFp LockTag::Fingerprint() const {
472+
return XXH64(str_.data(), str_.size(), 0x1C69B3F74AC4AE35UL);
473+
}
474+
448475
} // namespace dfly

src/server/common.h

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ using RdbTypeFreqMap = absl::flat_hash_map<unsigned, size_t>;
4848
constexpr DbIndex kInvalidDbId = DbIndex(-1);
4949
constexpr ShardId kInvalidSid = ShardId(-1);
5050
constexpr DbIndex kMaxDbId = 1024; // Reasonable starting point.
51+
using LockFp = uint64_t; // a key fingerprint used by the LockTable.
5152

5253
class CommandId;
5354
class Transaction;
@@ -59,14 +60,14 @@ struct LockTagOptions {
5960
char close_locktag = '}';
6061
unsigned skip_n_end_delimiters = 0;
6162
std::string prefix;
62-
};
6363

64-
struct KeyLockArgs {
65-
static LockTagOptions GetLockTagOptions();
64+
// Returns the tag according to the rules defined by this options object.
65+
std::string_view Tag(std::string_view key) const;
6666

67-
// Before acquiring and releasing keys, one must "normalize" them via GetLockKey().
68-
static std::string_view GetLockKey(std::string_view key);
67+
static const LockTagOptions& instance();
68+
};
6969

70+
struct KeyLockArgs {
7071
DbIndex db_index = 0;
7172
ArgSlice args;
7273
unsigned key_step = 1;
@@ -117,6 +118,33 @@ struct OpArgs {
117118
}
118119
};
119120

121+
// A strong type for a lock tag. Helps to disambiguide between keys and the parts of the
122+
// keys that are used for locking.
123+
class LockTag {
124+
std::string_view str_;
125+
126+
public:
127+
using is_stackonly = void; // marks that this object does not use heap.
128+
129+
LockTag() = default;
130+
explicit LockTag(std::string_view key);
131+
132+
explicit operator std::string_view() const {
133+
return str_;
134+
}
135+
136+
LockFp Fingerprint() const;
137+
138+
// To make it hashable.
139+
template <typename H> friend H AbslHashValue(H h, const LockTag& tag) {
140+
return H::combine(std::move(h), tag.str_);
141+
}
142+
143+
bool operator==(const LockTag& o) const {
144+
return str_ == o.str_;
145+
}
146+
};
147+
120148
// Record non auto journal command with own txid and dbid.
121149
void RecordJournal(const OpArgs& op_args, std::string_view cmd, ArgSlice args,
122150
uint32_t shard_cnt = 1, bool multi_commands = false);

src/server/conn_context.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ size_t ConnectionState::ExecInfo::UsedMemory() const {
236236
}
237237

238238
size_t ConnectionState::ScriptInfo::UsedMemory() const {
239-
return dfly::HeapSize(keys) + async_cmds_heap_mem;
239+
return dfly::HeapSize(lock_tags) + async_cmds_heap_mem;
240240
}
241241

242242
size_t ConnectionState::SubscribeInfo::UsedMemory() const {

src/server/conn_context.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ struct ConnectionState {
100100
struct ScriptInfo {
101101
size_t UsedMemory() const;
102102

103-
absl::flat_hash_set<std::string_view> keys; // declared keys
103+
absl::flat_hash_set<LockTag> lock_tags; // declared tags
104104

105105
size_t async_cmds_heap_mem = 0; // bytes used by async_cmds
106106
size_t async_cmds_heap_limit = 0; // max bytes allowed for async_cmds

src/server/db_slice.cc

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ unsigned PrimeEvictionPolicy::Evict(const PrimeTable::HotspotBuckets& eb, PrimeT
198198
string scratch;
199199
string_view key = last_slot_it->first.GetSlice(&scratch);
200200
// do not evict locked keys
201-
if (lt.Find(KeyLockArgs::GetLockKey(key)).has_value())
201+
if (lt.Find(LockTag(key)).has_value())
202202
return 0;
203203

204204
// log the evicted keys to journal.
@@ -998,16 +998,16 @@ bool DbSlice::Acquire(IntentLock::Mode mode, const KeyLockArgs& lock_args) {
998998
bool lock_acquired = true;
999999

10001000
if (lock_args.args.size() == 1) {
1001-
string_view key = KeyLockArgs::GetLockKey(lock_args.args.front());
1002-
lock_acquired = lt.Acquire(key, mode);
1003-
uniq_keys_ = {key}; // needed only for tests.
1001+
LockTag tag(lock_args.args.front());
1002+
lock_acquired = lt.Acquire(tag, mode);
1003+
uniq_keys_ = {string_view(tag)}; // needed only for tests.
10041004
} else {
10051005
uniq_keys_.clear();
10061006

10071007
for (size_t i = 0; i < lock_args.args.size(); i += lock_args.key_step) {
1008-
string_view s = KeyLockArgs::GetLockKey(lock_args.args[i]);
1009-
if (uniq_keys_.insert(s).second) {
1010-
lock_acquired &= lt.Acquire(s, mode);
1008+
LockTag tag(lock_args.args[i]);
1009+
if (uniq_keys_.insert(string_view(tag)).second) {
1010+
lock_acquired &= lt.Acquire(tag, mode);
10111011
}
10121012
}
10131013
}
@@ -1018,13 +1018,12 @@ bool DbSlice::Acquire(IntentLock::Mode mode, const KeyLockArgs& lock_args) {
10181018
return lock_acquired;
10191019
}
10201020

1021-
void DbSlice::ReleaseNormalized(IntentLock::Mode mode, DbIndex db_index, std::string_view key) {
1022-
DCHECK_EQ(key, KeyLockArgs::GetLockKey(key));
1021+
void DbSlice::ReleaseNormalized(IntentLock::Mode mode, DbIndex db_index, LockTag tag) {
10231022
DVLOG(2) << "Release " << IntentLock::ModeName(mode) << " "
1024-
<< " for " << key;
1023+
<< " for " << string_view(tag);
10251024

10261025
auto& lt = db_arr_[db_index]->trans_locks;
1027-
lt.Release(KeyLockArgs::GetLockKey(key), mode);
1026+
lt.Release(tag, mode);
10281027
}
10291028

10301029
void DbSlice::Release(IntentLock::Mode mode, const KeyLockArgs& lock_args) {
@@ -1034,15 +1033,15 @@ void DbSlice::Release(IntentLock::Mode mode, const KeyLockArgs& lock_args) {
10341033

10351034
DVLOG(2) << "Release " << IntentLock::ModeName(mode) << " for " << lock_args.args[0];
10361035
if (lock_args.args.size() == 1) {
1037-
string_view key = KeyLockArgs::GetLockKey(lock_args.args.front());
1038-
ReleaseNormalized(mode, lock_args.db_index, key);
1036+
string_view key = lock_args.args.front();
1037+
ReleaseNormalized(mode, lock_args.db_index, LockTag{key});
10391038
} else {
10401039
auto& lt = db_arr_[lock_args.db_index]->trans_locks;
10411040
uniq_keys_.clear();
10421041
for (size_t i = 0; i < lock_args.args.size(); i += lock_args.key_step) {
1043-
string_view s = KeyLockArgs::GetLockKey(lock_args.args[i]);
1044-
if (uniq_keys_.insert(s).second) {
1045-
lt.Release(s, mode);
1042+
LockTag tag(lock_args.args[i]);
1043+
if (uniq_keys_.insert(string_view(tag)).second) {
1044+
lt.Release(tag, mode);
10461045
}
10471046
}
10481047
}
@@ -1051,9 +1050,9 @@ void DbSlice::Release(IntentLock::Mode mode, const KeyLockArgs& lock_args) {
10511050

10521051
bool DbSlice::CheckLock(IntentLock::Mode mode, DbIndex dbid, string_view key) const {
10531052
const auto& lt = db_arr_[dbid]->trans_locks;
1054-
string_view s = KeyLockArgs::GetLockKey(key);
1053+
LockTag tag(key);
10551054

1056-
auto lock = lt.Find(s);
1055+
auto lock = lt.Find(tag);
10571056
if (lock) {
10581057
return lock->Check(mode);
10591058
}
@@ -1322,7 +1321,7 @@ void DbSlice::FreeMemWithEvictionStep(DbIndex db_ind, size_t increase_goal_bytes
13221321
// check if the key is locked by looking up transaction table.
13231322
const auto& lt = db_table->trans_locks;
13241323
string_view key = evict_it->first.GetSlice(&tmp);
1325-
if (lt.Find(KeyLockArgs::GetLockKey(key)).has_value())
1324+
if (lt.Find(LockTag(key)).has_value())
13261325
continue;
13271326

13281327
if (auto journal = owner_->journal(); journal) {

src/server/db_slice.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,8 @@ class DbSlice {
477477
void PerformDeletion(Iterator del_it, DbTable* table);
478478
void PerformDeletion(PrimeIterator del_it, DbTable* table);
479479

480-
// Releases a single key. `key` must have been normalized by GetLockKey().
481-
void ReleaseNormalized(IntentLock::Mode m, DbIndex db_index, std::string_view key);
480+
// Releases a single tag.
481+
void ReleaseNormalized(IntentLock::Mode m, DbIndex db_index, LockTag tag);
482482

483483
private:
484484
void PreUpdate(DbIndex db_ind, Iterator it);

0 commit comments

Comments
 (0)