Skip to content

Commit 383ac33

Browse files
committed
feat(server): Serializer bucket states
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
1 parent c7de4e6 commit 383ac33

4 files changed

Lines changed: 71 additions & 116 deletions

File tree

src/server/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ helio_cxx_test(cluster/cluster_config_test dfly_test_lib LABELS DFLY)
160160
helio_cxx_test(cluster/cluster_family_test dfly_test_lib LABELS DFLY)
161161
helio_cxx_test(acl/acl_family_test dfly_test_lib LABELS DFLY)
162162
helio_cxx_test(engine_shard_set_test dfly_test_lib LABELS DFLY)
163-
helio_cxx_test(serializer_base_test dfly_test_lib LABELS DFLY)
164163

165164
add_dependencies(check_dfly dragonfly_test json_family_test list_family_test
166165
generic_family_test memcache_parser_test rdb_test journal_test

src/server/serializer_base.cc

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,35 @@
1515

1616
namespace dfly {
1717

18+
void BucketDependencies::Increment(BucketIdentity bucket) {
19+
auto& counter = deps_[bucket];
20+
if (!counter)
21+
counter = std::make_shared<LocalLatch>();
22+
counter->lock();
23+
}
24+
25+
void BucketDependencies::Decrement(BucketIdentity bucket) {
26+
auto it = deps_.find(bucket);
27+
DCHECK(it != deps_.end());
28+
it->second->unlock();
29+
30+
if (!it->second->IsBlocked())
31+
deps_.erase(it);
32+
}
33+
34+
void BucketDependencies::Wait(BucketIdentity bucket) const {
35+
auto it = deps_.find(bucket);
36+
if (it == deps_.end())
37+
return;
38+
39+
auto counter = it->second; // copy value for address stability
40+
counter->Wait();
41+
}
42+
43+
bool BucketDependencies::DEBUG_IsBusy(BucketIdentity bucket) const {
44+
return deps_.contains(bucket);
45+
}
46+
1847
void DelayedEntryHandler::EnqueueOffloaded(BucketIdentity bucket, DbIndex db_index, PrimeKey pk,
1948
const PrimeValue& pv, time_t expire_time,
2049
uint32_t mc_flags) {
@@ -26,6 +55,8 @@ void DelayedEntryHandler::EnqueueOffloaded(BucketIdentity bucket, DbIndex db_ind
2655
auto future = ReadTieredString(db_index, key, pv, EngineShard::tlocal()->tiered_storage());
2756
auto entry = std::make_unique<TieredDelayedEntry>(db_index, std::move(pk), std::move(future),
2857
expire_time, mc_flags);
58+
59+
deps_.Increment(bucket);
2960
delayed_entries_.emplace(bucket, std::move(entry));
3061
}
3162

@@ -35,7 +66,7 @@ void DelayedEntryHandler::ProcessDelayedEntries(bool force, BucketIdentity flush
3566
if (delayed_entries_.size() > kMaxDelayedEntries)
3667
force |= true;
3768

38-
auto serialize_entry = [&](auto it) {
69+
auto serialize_entry = [&](decltype(delayed_entries_)::iterator it) {
3970
auto& entry = it->second;
4071
auto value = entry->value.Get();
4172

@@ -47,6 +78,8 @@ void DelayedEntryHandler::ProcessDelayedEntries(bool force, BucketIdentity flush
4778

4879
PrimeValue pv{*value};
4980
SerializeFetchedEntry(*entry, pv);
81+
82+
deps_.Decrement(it->first);
5083
delayed_entries_.erase(it++);
5184
};
5285

@@ -68,8 +101,9 @@ void DelayedEntryHandler::ProcessDelayedEntries(bool force, BucketIdentity flush
68101
}
69102

70103
SerializerBase::SerializerBase(DbSlice* slice, ExecutionState* cntx)
71-
: db_slice_(slice), base_cntx_(cntx) {
72-
DCHECK(db_slice_);
104+
: DelayedEntryHandler(static_cast<BucketDependencies&>(*this)),
105+
db_slice_(slice),
106+
base_cntx_(cntx) {
73107
DCHECK(base_cntx_);
74108
}
75109

@@ -97,45 +131,19 @@ void SerializerBase::UnregisterChangeListener() {
97131
db_slice_->UnregisterOnChange(version);
98132
}
99133

100-
void SerializerBase::MarkBucketSerializing(BucketIdentity bid) {
101-
DCHECK(!bucket_states_.contains(bid)) << "Bucket already in transient state";
102-
bucket_states_[bid] = BucketPhase::kSerializing;
103-
}
104-
105-
void SerializerBase::FinishBucketIteration(BucketIdentity bid) {
106-
auto it = bucket_states_.find(bid);
107-
DCHECK(it != bucket_states_.end());
108-
DCHECK(it->second == BucketPhase::kSerializing);
109-
110-
bucket_states_.erase(it);
111-
++stats_.buckets_serialized;
112-
}
113-
114-
bool SerializerBase::ShouldProcessBucket(PrimeTable::bucket_iterator it) {
115-
// Check if bucket is invalid or was already serialized
116-
if (it.is_done() || it.GetVersion() >= snapshot_version_) {
117-
++stats_.buckets_skipped;
118-
return false;
119-
}
120-
121-
// Check if this bucket is currently being serialized
122-
if (bucket_states_.contains(it.bucket_address())) {
123-
++stats_.change_during_serialization;
124-
return false;
125-
}
126-
127-
return true;
128-
}
129-
130134
bool SerializerBase::ProcessBucket(DbIndex db_index, PrimeTable::bucket_iterator it,
131135
bool on_update) {
132136
std::lock_guard guard(big_value_mu_);
133137

134-
// Check if this bucket should be serialized
135-
if (!ShouldProcessBucket(it)) {
138+
// Check if this bucket is stale
139+
if (it.is_done() || it.GetVersion() >= snapshot_version_) {
136140
// Force flush all delayed entries in the touched bucket
137141
if (EngineShard::tlocal()->tiered_storage() != nullptr && on_update && !it.is_done())
138142
ProcessDelayedEntries(false, it.bucket_address(), base_cntx_);
143+
144+
// Expected to be fully serialized due to big_value_mu_ guarding all paths
145+
// Otherwise, this needs to be changed to a wait
146+
DCHECK(!BucketDependencies::DEBUG_IsBusy(it.bucket_address()));
139147
return false;
140148
}
141149

@@ -148,9 +156,11 @@ bool SerializerBase::ProcessBucket(DbIndex db_index, PrimeTable::bucket_iterator
148156
}
149157

150158
it.SetVersion(snapshot_version_);
151-
MarkBucketSerializing(it.bucket_address());
159+
BucketDependencies::Increment(it.bucket_address());
160+
152161
stats_.keys_serialized += SerializeBucket(db_index, it, on_update);
153-
FinishBucketIteration(it.bucket_address());
162+
stats_.buckets_serialized++;
163+
BucketDependencies::Decrement(it.bucket_address());
154164

155165
if (EngineShard::tlocal()->tiered_storage() != nullptr)
156166
ProcessDelayedEntries(false, on_update ? it.bucket_address() : 0, base_cntx_);

src/server/serializer_base.h

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66

77
#include <absl/container/flat_hash_map.h>
88

9+
#include <memory>
910
#include <vector>
1011

1112
#include "server/db_slice.h"
1213
#include "server/journal/types.h"
1314
#include "server/synchronization.h"
1415
#include "server/table.h"
1516
#include "server/tiered_storage.h"
17+
#include "util/fibers/synchronization.h"
1618

1719
namespace dfly {
1820

@@ -22,6 +24,21 @@ class ExecutionState;
2224
// Unique across all databases/segments for the lifetime of a serialization.
2325
using BucketIdentity = uintptr_t;
2426

27+
// Track dependencies for buckets
28+
struct BucketDependencies {
29+
// Increase number of dependencies for bucket
30+
void Increment(BucketIdentity bucket);
31+
void Decrement(BucketIdentity bucket);
32+
33+
// Wait for all bucket dependencies to resolve
34+
void Wait(BucketIdentity bucket) const;
35+
bool DEBUG_IsBusy(BucketIdentity) const;
36+
37+
private:
38+
using SharedLatch = std::shared_ptr<LocalLatch>;
39+
absl::flat_hash_map<BucketIdentity, SharedLatch> deps_;
40+
};
41+
2542
// Tracks serialization progress of offloaded (delayed) entries.
2643
struct DelayedEntryHandler {
2744
void EnqueueOffloaded(BucketIdentity bucket, DbIndex db_index, PrimeKey pk, const PrimeValue& pv,
@@ -35,7 +52,13 @@ struct DelayedEntryHandler {
3552
// Serialize delayed entry that was fetched with serializer specific implementation
3653
virtual void SerializeFetchedEntry(const TieredDelayedEntry& tde, const PrimeValue& pv) = 0;
3754

55+
protected:
56+
explicit DelayedEntryHandler(BucketDependencies& deps) : deps_{deps} {
57+
}
58+
3859
private:
60+
BucketDependencies& deps_;
61+
3962
// Entries that are waiting for tiered storage reads to complete before they can be serialized.
4063
std::multimap<BucketIdentity, std::unique_ptr<TieredDelayedEntry>> delayed_entries_;
4164
};
@@ -50,7 +73,7 @@ struct DelayedEntryHandler {
5073
//
5174
// State tracking is purely observational in early PRs: it drives DCHECKs and
5275
// stats but does not alter the serialization control flow.
53-
class SerializerBase : public DelayedEntryHandler {
76+
class SerializerBase : public BucketDependencies, public DelayedEntryHandler {
5477
public:
5578
struct Stats {
5679
uint64_t keys_serialized = 0; // total number of keys serialized
@@ -108,21 +131,7 @@ class SerializerBase : public DelayedEntryHandler {
108131

109132
private:
110133
friend class SerializerBaseTest;
111-
SerializerBase() : db_slice_(nullptr), base_cntx_(nullptr) {
112-
}
113-
114-
// Return identity if bucket should be processed.
115-
// Checks bucket validity, version and state
116-
bool ShouldProcessBucket(PrimeTable::bucket_iterator);
117-
118-
// Transition bucket from NotVisited -> Serializing.
119-
void MarkBucketSerializing(BucketIdentity bid);
120-
121-
// Transition bucket from Serializing -> Covered (empty delayed) or
122-
// Serializing -> DelayedPending (non-empty delayed).
123-
void FinishBucketIteration(BucketIdentity bid);
124134

125-
absl::flat_hash_map<BucketIdentity, BucketPhase> bucket_states_;
126135
uint64_t change_cb_id_ = 0;
127136
};
128137

src/server/serializer_base_test.cc

Lines changed: 0 additions & 63 deletions
This file was deleted.

0 commit comments

Comments
 (0)