Skip to content

Commit 37cf721

Browse files
jimwwalkerdaverigby
authored andcommitted
MB-15009: 3/3 Defragment HashTable - StoredValue
Building on the existing value defragmenter, we now defragment StoredValues for persistent buckets. The StoredValue carries an age (hidden in the value tag) which when it reaches a new threshold, triggers the defragger to ask the HashTable to reallocate the stored value. Change-Id: I4b50ebe4a7abc50e15389bc1d7dea4301543a174 Reviewed-on: http://review.couchbase.org/106450 Well-Formed: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]> Tested-by: Build Bot <[email protected]>
1 parent ecc244d commit 37cf721

21 files changed

+372
-71
lines changed

engines/ep/benchmarks/defragmenter_bench.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ class DefragmentBench : public benchmark::Fixture {
103103
// the given age.
104104
DefragmentVisitor visitor(age_threshold,
105105
DefragmenterTask::getMaxValueSize(
106-
get_mock_server_api()->alloc_hooks));
106+
get_mock_server_api()->alloc_hooks),
107+
age_threshold);
107108
// Need to run 10 passes; so we allow the deframenter to defrag at
108109
// least once (given the age_threshold may be up to 10).
109110
const size_t passes = 10;

engines/ep/configuration.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@
289289
"dynamic": false,
290290
"type": "std::string"
291291
},
292-
"dcp_noop_mandatory_for_v5_features": {
292+
"dcp_noop_mandatory_for_v5_features": {
293293
"default": "true",
294294
"descr": "Forces clients to enable noop for v5 features",
295295
"type": "bool"
@@ -307,7 +307,12 @@
307307
},
308308
"defragmenter_age_threshold": {
309309
"default": "10",
310-
"descr": "How old (measured in number of defragmenter passes) must a document be to be considered for degragmentation.",
310+
"descr": "How old (measured in number of DefragmenterVisitor passes) must a document be to be considered for defragmentation.",
311+
"type": "size_t"
312+
},
313+
"defragmenter_stored_value_age_threshold": {
314+
"default": "10",
315+
"descr": "How old (measured in number of DefragmenterVisitor passes) must a StoredValue be to be considered for defragmentation.",
311316
"type": "size_t"
312317
},
313318
"defragmenter_chunk_duration": {

engines/ep/docs/stats.org

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ For introductory information on stats within Couchbase, start with the
300300
| | run (in seconds). |
301301
| ep_defragmenter_num_moved | Number of items moved by the |
302302
| | defragmentater task. |
303+
| ep_defragmenter_sv_num_moved | Number of StoredValues moved by the |
304+
| | defragmentater task. |
303305
| ep_defragmenter_num_visited | Number of items visited (considered |
304306
| | for defragmentation) by the |
305307
| | defragmenter task. |

engines/ep/src/defragmenter.cc

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,22 @@ bool DefragmenterTask::run(void) {
4040
// then resume from where we last were, otherwise create a new visitor
4141
// starting from the beginning.
4242
if (!prAdapter) {
43-
prAdapter = std::make_unique<PauseResumeVBAdapter>(
44-
std::make_unique<DefragmentVisitor>(
45-
getAgeThreshold(), getMaxValueSize(alloc_hooks)));
43+
boost::optional<uint8_t> storedValueAge;
44+
45+
// Only defragment persistent buckets because the HashTable defrag
46+
// method doesn't yet know how to maintain the ephemeral seqno
47+
// linked-list
48+
if (engine->getConfiguration().getBucketType() == "persistent") {
49+
storedValueAge = getStoredValueAgeThreshold();
50+
}
51+
52+
auto visitor = std::make_unique<DefragmentVisitor>(
53+
getAgeThreshold(),
54+
getMaxValueSize(alloc_hooks),
55+
storedValueAge);
56+
57+
prAdapter =
58+
std::make_unique<PauseResumeVBAdapter>(std::move(visitor));
4659
epstore_position = engine->getKVBucket()->startPosition();
4760
}
4861

@@ -80,9 +93,7 @@ bool DefragmenterTask::run(void) {
8093
// Defrag complete. Restore thread caching.
8194
alloc_hooks->enable_thread_cache(old_tcache);
8295

83-
// Update stats
84-
stats.defragNumMoved.fetch_add(visitor.getDefragCount());
85-
stats.defragNumVisited.fetch_add(visitor.getVisitedCount());
96+
updateStats(visitor);
8697

8798
// Release any free memory we now have in the allocator back to the OS.
8899
// TODO: Benchmark this - is it necessary? How much of a slowdown does it
@@ -153,6 +164,17 @@ size_t DefragmenterTask::getAgeThreshold() const {
153164
return engine->getConfiguration().getDefragmenterAgeThreshold();
154165
}
155166

167+
size_t DefragmenterTask::getStoredValueAgeThreshold() const {
168+
return engine->getConfiguration().getDefragmenterStoredValueAgeThreshold();
169+
}
170+
171+
void DefragmenterTask::updateStats(DefragmentVisitor& visitor) {
172+
stats.defragNumMoved.fetch_add(visitor.getDefragCount());
173+
stats.defragStoredValueNumMoved.fetch_add(
174+
visitor.getStoredValueDefragCount());
175+
stats.defragNumVisited.fetch_add(visitor.getVisitedCount());
176+
}
177+
156178
size_t DefragmenterTask::getMaxValueSize(ALLOCATOR_HOOKS_API* alloc_hooks) {
157179
size_t nbins{0};
158180
alloc_hooks->get_allocator_property("arenas.nbins", &nbins);

engines/ep/src/defragmenter.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ class DefragmenterTask : public GlobalTask {
123123
// must be to be considered for defragmentation.
124124
size_t getAgeThreshold() const;
125125

126+
// Minimum age (measured in defragmenter task passes) that a StoredValue
127+
// must be to be considered for defragmentation.
128+
size_t getStoredValueAgeThreshold() const;
129+
126130
// Upper limit on how long each defragmention chunk can run for, before
127131
// being paused.
128132
std::chrono::milliseconds getChunkDuration() const;
@@ -133,6 +137,9 @@ class DefragmenterTask : public GlobalTask {
133137
/// Returns the underlying DefragmentVisitor instance.
134138
DefragmentVisitor& getDefragVisitor();
135139

140+
/// Update the EPStats from the visitor
141+
void updateStats(DefragmentVisitor& visitor);
142+
136143
/// Reference to EP stats, used to check on mem_used.
137144
EPStats &stats;
138145

engines/ep/src/defragmenter_visitor.cc

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
// DegragmentVisitor implementation ///////////////////////////////////////////
2121

2222
DefragmentVisitor::DefragmentVisitor(uint8_t age_threshold_,
23-
size_t max_size_class)
23+
size_t max_size_class,
24+
boost::optional<uint8_t> sv_age_threshold)
2425
: max_size_class(max_size_class),
2526
age_threshold(age_threshold_),
2627
defrag_count(0),
2728
visited_count(0),
28-
currentVb(nullptr) {
29+
currentVb(nullptr),
30+
sv_age_threshold(sv_age_threshold) {
2931
}
3032

3133
DefragmentVisitor::~DefragmentVisitor() {
@@ -57,6 +59,15 @@ bool DefragmentVisitor::visit(const HashTable::HashBucketLock& lh,
5759
v.getValue()->incrementAge();
5860
}
5961
}
62+
63+
if (sv_age_threshold) {
64+
if (v.getAge() >= sv_age_threshold.get()) {
65+
defragmentStoredValue(v);
66+
} else {
67+
v.incrementAge();
68+
}
69+
}
70+
6071
visited_count++;
6172

6273
// See if we have done enough work for this chunk. If so
@@ -67,6 +78,7 @@ bool DefragmentVisitor::visit(const HashTable::HashBucketLock& lh,
6778
void DefragmentVisitor::clearStats() {
6879
defrag_count = 0;
6980
visited_count = 0;
81+
sv_defrag_count = 0;
7082
}
7183

7284
size_t DefragmentVisitor::getDefragCount() const {
@@ -77,6 +89,16 @@ size_t DefragmentVisitor::getVisitedCount() const {
7789
return visited_count;
7890
}
7991

92+
size_t DefragmentVisitor::getStoredValueDefragCount() const {
93+
return sv_defrag_count;
94+
}
95+
8096
void DefragmentVisitor::setCurrentVBucket(VBucket& vb) {
8197
currentVb = &vb;
8298
}
99+
100+
void DefragmentVisitor::defragmentStoredValue(StoredValue& v) const {
101+
if (currentVb->ht.reallocateStoredValue(std::forward<StoredValue>(v))) {
102+
sv_defrag_count++;
103+
}
104+
}

engines/ep/src/defragmenter_visitor.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131
*/
3232
class DefragmentVisitor : public VBucketAwareHTVisitor {
3333
public:
34-
DefragmentVisitor(uint8_t age_threshold_, size_t max_size_class);
34+
DefragmentVisitor(uint8_t age_threshold_,
35+
size_t max_size_class,
36+
boost::optional<uint8_t> sv_age_threshold);
3537

3638
~DefragmentVisitor();
3739

@@ -51,9 +53,15 @@ class DefragmentVisitor : public VBucketAwareHTVisitor {
5153
// Returns the number of documents that have been visited.
5254
size_t getVisitedCount() const;
5355

56+
// Returns the number of StoredValues that have been defragmented.
57+
size_t getStoredValueDefragCount() const;
58+
5459
void setCurrentVBucket(VBucket& vb) override;
5560

5661
private:
62+
/// Request to reallocate the StoredValue
63+
void defragmentStoredValue(StoredValue& v) const;
64+
5765
/* Configuration parameters */
5866

5967
// Size of the largest size class from the allocator.
@@ -72,7 +80,12 @@ class DefragmentVisitor : public VBucketAwareHTVisitor {
7280
size_t defrag_count;
7381
// How many documents have been visited.
7482
size_t visited_count;
83+
// How many stored-values have been defrag'd
84+
mutable size_t sv_defrag_count{0};
7585

7686
// The current vbucket that is being processed
7787
VBucket* currentVb;
88+
89+
// If defined, the age at which StoredValue's are de-fragmented
90+
boost::optional<uint8_t> sv_age_threshold;
7891
};

engines/ep/src/ep_engine.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2923,6 +2923,10 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::doEngineStats(const void *cookie,
29232923
add_stat, cookie);
29242924
add_casted_stat("ep_defragmenter_num_moved", epstats.defragNumMoved,
29252925
add_stat, cookie);
2926+
add_casted_stat("ep_defragmenter_sv_num_moved",
2927+
epstats.defragStoredValueNumMoved,
2928+
add_stat,
2929+
cookie);
29262930

29272931
add_casted_stat("ep_item_compressor_num_visited",
29282932
epstats.compressorNumVisited,

engines/ep/src/hash_table.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,21 @@ MutationStatus HashTable::insertFromWarmup(
569569
return MutationStatus::NotFound;
570570
}
571571

572+
bool HashTable::reallocateStoredValue(StoredValue&& sv) {
573+
// Search the chain and reallocate
574+
for (StoredValue::UniquePtr* curr =
575+
&values[getBucketForHash(sv.getKey().hash())];
576+
curr->get().get();
577+
curr = &curr->get()->getNext()) {
578+
if (&sv == curr->get().get()) {
579+
auto newSv = valFact->copyStoredValue(sv, std::move(sv.getNext()));
580+
curr->swap(newSv);
581+
return true;
582+
}
583+
}
584+
return false;
585+
}
586+
572587
void HashTable::dump() const {
573588
std::cerr << *this << std::endl;
574589
}

engines/ep/src/hash_table.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,19 @@ class HashTable {
798798
bool keyMetaDataOnly,
799799
item_eviction_policy_t evictionPolicy);
800800

801+
/**
802+
* 'Defragment' the StoredValue, this really means reallocate the object
803+
* which has the effect of repacking the underlying allocators memory pool.
804+
*
805+
* As the object is reallocated the input StoredValue is 'consumed', i.e.
806+
* it will be copied into a new allocate and the original freed, the
807+
* input is deleted on successful reallocation.
808+
*
809+
* @param v The rvalue reference to the StoredValue to be reallocated.
810+
* @return true if v was found and reallocated
811+
*/
812+
bool reallocateStoredValue(StoredValue&& v);
813+
801814
/**
802815
* Dump a representation of the HashTable to stderr.
803816
*/

0 commit comments

Comments
 (0)