Skip to content

Commit 6a470ac

Browse files
committed
MB-27554: [BP] Centralize HashTable count statistics
Originally 4f6873e HashTable maintains a number of statistics about how many StoredValues it contains which match some criteria - for example number of non-resident, number with a given datatype, etc. Ensuring the count of these statistics is accurate is currenly error-prone - each of the different operations which may result in the count changing needs to take care to update the relevent stat(s). To make this more robust, refactor the counting of these statistics so it is centrallised in two symmetric methods - statsPrologue() and statsEpilogue(). statsPrologue should be called before making any changes which may affect any of the stats (i.e. whenever a StoredValue is changed), and statsEpilogue should be called afterwards. By moving the all stats accounting to two methods (which are vitually identical) we should minimise any accounting issues. Change-Id: I33bb436a7a7d80d1c8569ce825ba215a0f47f14f Reviewed-on: http://review.couchbase.org/88377 Well-Formed: Build Bot <[email protected]> Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 793a949 commit 6a470ac

File tree

5 files changed

+107
-102
lines changed

5 files changed

+107
-102
lines changed

engines/ep/src/hash_table.cc

Lines changed: 57 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -277,34 +277,13 @@ MutationStatus HashTable::unlocked_updateStoredValue(
277277

278278
MutationStatus status =
279279
v.isDirty() ? MutationStatus::WasDirty : MutationStatus::WasClean;
280-
if (!v.isResident() && !v.isDeleted() && !v.isTempItem()) {
281-
decrNumNonResidentItems();
282-
}
283-
284-
if (v.isTempItem()) {
285-
--numTempItems;
286-
++numItems;
287-
++numTotalItems;
288-
}
289-
290-
if (v.isDeleted() && !itm.isDeleted()) {
291-
--numDeletedItems;
292-
}
293-
if (!v.isDeleted() && itm.isDeleted()) {
294-
++numDeletedItems;
295-
}
296280

297-
// Update datatype counts (which only count non-temp, non-deleted
298-
// documents).
299-
if (!v.isDeleted() && !v.isTempItem()) {
300-
--datatypeCounts[v.getDatatype()];
301-
}
302-
if (!itm.isDeleted()) {
303-
++datatypeCounts[itm.getDataType()];
304-
}
281+
statsPrologue(v);
305282

306283
/* setValue() will mark v as undeleted if required */
307-
setValue(itm, v);
284+
v.setValue(itm);
285+
286+
statsEpilogue(v);
308287

309288
return status;
310289
}
@@ -325,23 +304,55 @@ StoredValue* HashTable::unlocked_addNewStoredValue(const HashBucketLock& hbl,
325304

326305
// Create a new StoredValue and link it into the head of the bucket chain.
327306
auto v = (*valFact)(itm, std::move(values[hbl.getBucketNum()]));
328-
increaseMetaDataSize(stats, v->metaDataSize());
329-
increaseCacheSize(v->size());
330307

331-
if (v->isTempItem()) {
308+
statsEpilogue(*v);
309+
310+
values[hbl.getBucketNum()] = std::move(v);
311+
return values[hbl.getBucketNum()].get();
312+
}
313+
314+
void HashTable::statsPrologue(const StoredValue& v) {
315+
// Decrease all statistics which sv matches.
316+
reduceMetaDataSize(stats, v.metaDataSize());
317+
reduceCacheSize(v.size());
318+
319+
if (!v.isResident() && !v.isDeleted() && !v.isTempItem()) {
320+
decrNumNonResidentItems();
321+
}
322+
323+
if (v.isTempItem()) {
324+
--numTempItems;
325+
} else {
326+
--numItems;
327+
--numTotalItems;
328+
if (v.isDeleted()) {
329+
--numDeletedItems;
330+
} else {
331+
--datatypeCounts[v.getDatatype()];
332+
}
333+
}
334+
}
335+
336+
void HashTable::statsEpilogue(const StoredValue& v) {
337+
// After performing updates to sv; increase all statistics which sv matches.
338+
increaseMetaDataSize(stats, v.metaDataSize());
339+
increaseCacheSize(v.size());
340+
341+
if (!v.isResident() && !v.isDeleted() && !v.isTempItem()) {
342+
++numNonResidentItems;
343+
}
344+
345+
if (v.isTempItem()) {
332346
++numTempItems;
333347
} else {
334348
++numItems;
335349
++numTotalItems;
350+
if (v.isDeleted()) {
351+
++numDeletedItems;
352+
} else {
353+
++datatypeCounts[v.getDatatype()];
354+
}
336355
}
337-
if (v->isDeleted()) {
338-
++numDeletedItems;
339-
} else {
340-
++datatypeCounts[v->getDatatype()];
341-
}
342-
values[hbl.getBucketNum()] = std::move(v);
343-
344-
return values[hbl.getBucketNum()].get();
345356
}
346357

347358
std::pair<StoredValue*, StoredValue::UniquePtr>
@@ -365,53 +376,26 @@ HashTable::unlocked_replaceByCopy(const HashBucketLock& hbl,
365376
/* Copy the StoredValue and link it into the head of the bucket chain. */
366377
auto newSv = valFact->copyStoredValue(
367378
vToCopy, std::move(values[hbl.getBucketNum()]));
368-
increaseMetaDataSize(stats, newSv->metaDataSize());
369-
increaseCacheSize(newSv->size());
370379

371-
if (newSv->isTempItem()) {
372-
++numTempItems;
373-
} else {
374-
++numItems;
375-
++numTotalItems;
376-
}
377-
if (newSv->isDeleted()) {
378-
++numDeletedItems;
379-
} else {
380-
++datatypeCounts[newSv->getDatatype()];
381-
}
382-
values[hbl.getBucketNum()] = std::move(newSv);
380+
// Adding a new item into the HashTable; update stats.
381+
statsEpilogue(*newSv);
383382

383+
values[hbl.getBucketNum()] = std::move(newSv);
384384
return {values[hbl.getBucketNum()].get(), std::move(releasedSv)};
385385
}
386386

387387
void HashTable::unlocked_softDelete(const std::unique_lock<std::mutex>& htLock,
388388
StoredValue& v,
389389
bool onlyMarkDeleted) {
390-
const bool alreadyDeleted = v.isDeleted();
391-
if (!v.isResident() && !v.isDeleted() && !v.isTempItem()) {
392-
decrNumNonResidentItems();
393-
}
394-
395-
if (!alreadyDeleted) {
396-
--datatypeCounts[v.getDatatype()];
397-
}
390+
statsPrologue(v);
398391

399392
if (onlyMarkDeleted) {
400393
v.markDeleted();
401394
} else {
402-
if (v.isTempItem()) {
403-
--numTempItems;
404-
++numItems;
405-
++numTotalItems;
406-
}
407-
size_t len = v.valuelen();
408-
if (v.del()) {
409-
reduceCacheSize(len);
410-
}
411-
}
412-
if (!alreadyDeleted) {
413-
++numDeletedItems;
395+
v.del();
414396
}
397+
398+
statsEpilogue(v);
415399
}
416400

417401
StoredValue* HashTable::unlocked_find(const DocKey& key,
@@ -464,20 +448,9 @@ StoredValue::UniquePtr HashTable::unlocked_release(
464448
"not found in HashTable; possibly HashTable leak");
465449
}
466450

467-
// Update statistics now the item has been removed.
468-
reduceCacheSize(released->size());
469-
reduceMetaDataSize(stats, released->metaDataSize());
470-
if (released->isTempItem()) {
471-
--numTempItems;
472-
} else {
473-
decrNumItems();
474-
decrNumTotalItems();
475-
if (released->isDeleted()) {
476-
--numDeletedItems;
477-
} else {
478-
--datatypeCounts[released->getDatatype()];
479-
}
480-
}
451+
// Update statistics for the item which is now gone.
452+
statsPrologue(*released);
453+
481454
return released;
482455
}
483456

@@ -829,12 +802,6 @@ void HashTable::reduceMetaDataSize(EPStats &st, size_t by) {
829802
st.currentSize.fetch_sub(by);
830803
}
831804

832-
void HashTable::setValue(const Item& itm, StoredValue& v) {
833-
reduceCacheSize(v.size());
834-
v.setValue(itm);
835-
increaseCacheSize(v.size());
836-
}
837-
838805
std::ostream& operator<<(std::ostream& os, const HashTable& ht) {
839806
os << "HashTable[" << &ht << "] with"
840807
<< " numItems:" << ht.getNumItems()

engines/ep/src/hash_table.h

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -614,18 +614,35 @@ class HashTable {
614614

615615
private:
616616
/**
617-
* Set item into StoredValue.
618-
* WARNING: Correct use of this method requires care, it directly updates
619-
* the state of a StoredValue without updating the related HashTable
620-
* counters, callers are responsible for ensuring the various counters
621-
* (numDeletedItems, numNonResident, etc) are correct after calling this
622-
* method.
623-
*
624-
* @param itm the Item to store
625-
* @param v the stored value in which itm needs
626-
* to be stored
627-
*/
628-
void setValue(const Item& itm, StoredValue& v);
617+
* Update HashTable statistics before modifying a StoredValue.
618+
*
619+
* This function should be called before modifying *any* StoredValue object,
620+
* if modifying it may affect any of the HashTable counts.
621+
* For example, before we remove a StoredValue we must decrement any counts
622+
* which it matches; or before we change its datatype we must decrement the
623+
* count of the old datatype.
624+
*
625+
* It is typically paired with statsPrologue if modifying an existing SV.
626+
* It will be used by itself if removing a SV (as there will be no SV
627+
* after to call statsEpilogue() with).
628+
*
629+
* See also: statsEpilogue().
630+
* @param sv StoredValue which is about to be modified.
631+
*/
632+
void statsPrologue(const StoredValue& sv);
633+
634+
/**
635+
* Update HashTable statistics after modifying a StoredValue.
636+
*
637+
* This function should be called after modifying *any* StoredValue object,
638+
* if modifying it may affect any of the HashTable counts.
639+
* For example, if the datatype of a StoredValue may have changed; then
640+
* datatypeCounts needs to be updated.
641+
*
642+
* See also: statsPrologue().
643+
* @param sv StoredValue which has just been modified.
644+
*/
645+
void statsEpilogue(const StoredValue& sv);
629646

630647
// The container for actually holding the StoredValues.
631648
using table_type = std::vector<StoredValue::UniquePtr>;

engines/ep/src/stored-value.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include <platform/cb_malloc.h>
2828

29+
const int64_t StoredValue::state_pending_seqno = -2;
2930
const int64_t StoredValue::state_deleted_key = -3;
3031
const int64_t StoredValue::state_non_existent_key = -4;
3132
const int64_t StoredValue::state_temp_init = -5;
@@ -280,6 +281,7 @@ bool StoredValue::deleteImpl() {
280281

281282
resetValue();
282283
setDatatype(PROTOCOL_BINARY_RAW_BYTES);
284+
setPendingSeqno();
283285

284286
setDeletedPriv(true);
285287
markDirty();

engines/ep/src/stored-value.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,14 @@ class StoredValue {
364364
bySeqno = state_non_existent_key;
365365
}
366366

367+
/**
368+
* Marks that the item's sequence number is pending (valid, non-temp; but
369+
* final value not yet known.
370+
*/
371+
void setPendingSeqno() {
372+
bySeqno = state_pending_seqno;
373+
}
374+
367375
/**
368376
* Is this a temporary item created for processing a get-meta request?
369377
*/
@@ -539,6 +547,15 @@ class StoredValue {
539547
* objects.
540548
*/
541549

550+
/**
551+
* Represents the state when a StoredValue is in the process of having its
552+
* seqno updated (and so _will be_ non-temporary; but we don't yet have the
553+
* new sequence number. This is unfortunately required due to limitations
554+
* in sequence number generation; where we need delete an item in the
555+
* HashTable before we know what its sequence number is going to be.
556+
*/
557+
static const int64_t state_pending_seqno;
558+
542559
/// Represents an item that's deleted from memory but present on disk.
543560
static const int64_t state_deleted_key;
544561

engines/ep/tests/module_tests/ephemeral_vb_test.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ TEST_F(EphemeralVBucketTest, AddTempItemAndUpdate) {
210210

211211
/* Add temp item */
212212
EXPECT_EQ(AddStatus::BgFetch, addOneTemp(k));
213+
ASSERT_EQ(0, vbucket->getNumItems());
213214

214215
/* Update the temp item (make it non-temp) */
215216
Item i(k, 0, /*expiry*/ 0, k.data(), k.size());
@@ -232,6 +233,7 @@ TEST_F(EphemeralVBucketTest, AddTempItemAndSoftDelete) {
232233

233234
/* Add temp item */
234235
EXPECT_EQ(AddStatus::BgFetch, addOneTemp(k));
236+
ASSERT_EQ(1, vbucket->getNumTempItems());
235237

236238
/* SoftDelete the temp item (make it non-temp) */
237239
softDeleteOne(k, MutationStatus::WasClean);

0 commit comments

Comments
 (0)