Skip to content

Commit 539dd20

Browse files
author
Lei Jin
committed
using thread local SuperVersion for NewIterator
Summary: Similar to GetImp(), use SuperVersion from thread local instead of acquriing mutex. I don't expect this change will make a dent on NewIterator() performance because the bottleneck seems to be on the rest part of the API Test Plan: make asan_check will post perf numbers Reviewers: haobo, igor, sdong, dhruba, yhchiang Reviewed By: sdong CC: leveldb Differential Revision: https://reviews.facebook.net/D17643
1 parent d5e087b commit 539dd20

File tree

3 files changed

+104
-69
lines changed

3 files changed

+104
-69
lines changed

db/column_family.cc

+85
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,87 @@ Compaction* ColumnFamilyData::CompactRange(int input_level, int output_level,
298298
begin, end, compaction_end);
299299
}
300300

301+
SuperVersion* ColumnFamilyData::GetReferencedSuperVersion(
302+
port::Mutex* db_mutex) {
303+
SuperVersion* sv = nullptr;
304+
if (LIKELY(column_family_set_->db_options_->allow_thread_local)) {
305+
sv = GetThreadLocalSuperVersion(db_mutex);
306+
sv->Ref();
307+
if (!ReturnThreadLocalSuperVersion(sv)) {
308+
sv->Unref();
309+
}
310+
} else {
311+
db_mutex->Lock();
312+
sv = super_version_->Ref();
313+
db_mutex->Unlock();
314+
}
315+
return sv;
316+
}
317+
318+
SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(
319+
port::Mutex* db_mutex) {
320+
SuperVersion* sv = nullptr;
321+
// The SuperVersion is cached in thread local storage to avoid acquiring
322+
// mutex when SuperVersion does not change since the last use. When a new
323+
// SuperVersion is installed, the compaction or flush thread cleans up
324+
// cached SuperVersion in all existing thread local storage. To avoid
325+
// acquiring mutex for this operation, we use atomic Swap() on the thread
326+
// local pointer to guarantee exclusive access. If the thread local pointer
327+
// is being used while a new SuperVersion is installed, the cached
328+
// SuperVersion can become stale. In that case, the background thread would
329+
// have swapped in kSVObsolete. We re-check the value at when returning
330+
// SuperVersion back to thread local, with an atomic compare and swap.
331+
// The superversion will need to be released if detected to be stale.
332+
void* ptr = local_sv_->Swap(SuperVersion::kSVInUse);
333+
// Invariant:
334+
// (1) Scrape (always) installs kSVObsolete in ThreadLocal storage
335+
// (2) the Swap above (always) installs kSVInUse, ThreadLocal storage
336+
// should only keep kSVInUse before ReturnThreadLocalSuperVersion call
337+
// (if no Scrape happens).
338+
assert(ptr != SuperVersion::kSVInUse);
339+
sv = static_cast<SuperVersion*>(ptr);
340+
if (sv == SuperVersion::kSVObsolete ||
341+
sv->version_number != super_version_number_.load()) {
342+
RecordTick(options_.statistics.get(), NUMBER_SUPERVERSION_ACQUIRES);
343+
SuperVersion* sv_to_delete = nullptr;
344+
345+
if (sv && sv->Unref()) {
346+
RecordTick(options_.statistics.get(), NUMBER_SUPERVERSION_CLEANUPS);
347+
db_mutex->Lock();
348+
// NOTE: underlying resources held by superversion (sst files) might
349+
// not be released until the next background job.
350+
sv->Cleanup();
351+
sv_to_delete = sv;
352+
} else {
353+
db_mutex->Lock();
354+
}
355+
sv = super_version_->Ref();
356+
db_mutex->Unlock();
357+
358+
delete sv_to_delete;
359+
}
360+
assert(sv != nullptr);
361+
return sv;
362+
}
363+
364+
bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
365+
assert(sv != nullptr);
366+
// Put the SuperVersion back
367+
void* expected = SuperVersion::kSVInUse;
368+
if (local_sv_->CompareAndSwap(static_cast<void*>(sv), expected)) {
369+
// When we see kSVInUse in the ThreadLocal, we are sure ThreadLocal
370+
// storage has not been altered and no Scrape has happend. The
371+
// SuperVersion is still current.
372+
return true;
373+
} else {
374+
// ThreadLocal scrape happened in the process of this GetImpl call (after
375+
// thread local Swap() at the beginning and before CompareAndSwap()).
376+
// This means the SuperVersion it holds is obsolete.
377+
assert(expected == SuperVersion::kSVObsolete);
378+
}
379+
return false;
380+
}
381+
301382
SuperVersion* ColumnFamilyData::InstallSuperVersion(
302383
SuperVersion* new_superversion, port::Mutex* db_mutex) {
303384
new_superversion->db_mutex = db_mutex;
@@ -306,6 +387,10 @@ SuperVersion* ColumnFamilyData::InstallSuperVersion(
306387
super_version_ = new_superversion;
307388
++super_version_number_;
308389
super_version_->version_number = super_version_number_;
390+
// Reset SuperVersions cached in thread local storage
391+
if (column_family_set_->db_options_->allow_thread_local) {
392+
ResetThreadLocalSuperVersions();
393+
}
309394
if (old_superversion != nullptr && old_superversion->Unref()) {
310395
old_superversion->Cleanup();
311396
return old_superversion; // will let caller delete outside of mutex

db/column_family.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,16 @@ class ColumnFamilyData {
199199

200200
SuperVersion* GetSuperVersion() { return super_version_; }
201201
// thread-safe
202-
ThreadLocalPtr* GetThreadLocalSuperVersion() const { return local_sv_.get(); }
202+
// Return a already referenced SuperVersion to be used safely.
203+
SuperVersion* GetReferencedSuperVersion(port::Mutex* db_mutex);
204+
// thread-safe
205+
// Get SuperVersion stored in thread local storage. If it does not exist,
206+
// get a reference from a current SuperVersion.
207+
SuperVersion* GetThreadLocalSuperVersion(port::Mutex* db_mutex);
208+
// Try to return SuperVersion back to thread local storage. Retrun true on
209+
// success and false on failure. It fails when the thread local storage
210+
// contains anything other than SuperVersion::kSVInUse flag.
211+
bool ReturnThreadLocalSuperVersion(SuperVersion* sv);
203212
// thread-safe
204213
uint64_t GetSuperVersionNumber() const {
205214
return super_version_number_.load();

db/db_impl.cc

+9-68
Original file line numberDiff line numberDiff line change
@@ -3179,8 +3179,7 @@ struct IterState {
31793179
static void CleanupIteratorState(void* arg1, void* arg2) {
31803180
IterState* state = reinterpret_cast<IterState*>(arg1);
31813181

3182-
bool need_cleanup = state->super_version->Unref();
3183-
if (need_cleanup) {
3182+
if (state->super_version->Unref()) {
31843183
DBImpl::DeletionState deletion_state;
31853184

31863185
state->mu->Lock();
@@ -3318,10 +3317,6 @@ void DBImpl::InstallSuperVersion(ColumnFamilyData* cfd,
33183317
cfd->InstallSuperVersion(new_superversion, &mutex_);
33193318
deletion_state.new_superversion = nullptr;
33203319
deletion_state.superversions_to_free.push_back(old_superversion);
3321-
// Reset SuperVersions cached in thread local storage
3322-
if (options_.allow_thread_local) {
3323-
cfd->ResetThreadLocalSuperVersions();
3324-
}
33253320
}
33263321

33273322
Status DBImpl::GetImpl(const ReadOptions& options,
@@ -3342,47 +3337,9 @@ Status DBImpl::GetImpl(const ReadOptions& options,
33423337

33433338
// Acquire SuperVersion
33443339
SuperVersion* sv = nullptr;
3345-
ThreadLocalPtr* thread_local_sv = nullptr;
3340+
// TODO(ljin): consider using GetReferencedSuperVersion() directly
33463341
if (LIKELY(options_.allow_thread_local)) {
3347-
// The SuperVersion is cached in thread local storage to avoid acquiring
3348-
// mutex when SuperVersion does not change since the last use. When a new
3349-
// SuperVersion is installed, the compaction or flush thread cleans up
3350-
// cached SuperVersion in all existing thread local storage. To avoid
3351-
// acquiring mutex for this operation, we use atomic Swap() on the thread
3352-
// local pointer to guarantee exclusive access. If the thread local pointer
3353-
// is being used while a new SuperVersion is installed, the cached
3354-
// SuperVersion can become stale. In that case, the background thread would
3355-
// have swapped in kSVObsolete. We re-check the value at the end of
3356-
// Get, with an atomic compare and swap. The superversion will be released
3357-
// if detected to be stale.
3358-
thread_local_sv = cfd->GetThreadLocalSuperVersion();
3359-
void* ptr = thread_local_sv->Swap(SuperVersion::kSVInUse);
3360-
// Invariant:
3361-
// (1) Scrape (always) installs kSVObsolete in ThreadLocal storage
3362-
// (2) the Swap above (always) installs kSVInUse, ThreadLocal storage
3363-
// should only keep kSVInUse during a GetImpl.
3364-
assert(ptr != SuperVersion::kSVInUse);
3365-
sv = static_cast<SuperVersion*>(ptr);
3366-
if (sv == SuperVersion::kSVObsolete ||
3367-
sv->version_number != cfd->GetSuperVersionNumber()) {
3368-
RecordTick(options_.statistics.get(), NUMBER_SUPERVERSION_ACQUIRES);
3369-
SuperVersion* sv_to_delete = nullptr;
3370-
3371-
if (sv && sv->Unref()) {
3372-
RecordTick(options_.statistics.get(), NUMBER_SUPERVERSION_CLEANUPS);
3373-
mutex_.Lock();
3374-
// TODO underlying resources held by superversion (sst files) might
3375-
// not be released until the next background job.
3376-
sv->Cleanup();
3377-
sv_to_delete = sv;
3378-
} else {
3379-
mutex_.Lock();
3380-
}
3381-
sv = cfd->GetSuperVersion()->Ref();
3382-
mutex_.Unlock();
3383-
3384-
delete sv_to_delete;
3385-
}
3342+
sv = cfd->GetThreadLocalSuperVersion(&mutex_);
33863343
} else {
33873344
mutex_.Lock();
33883345
sv = cfd->GetSuperVersion()->Ref();
@@ -3429,19 +3386,7 @@ Status DBImpl::GetImpl(const ReadOptions& options,
34293386

34303387
bool unref_sv = true;
34313388
if (LIKELY(options_.allow_thread_local)) {
3432-
// Put the SuperVersion back
3433-
void* expected = SuperVersion::kSVInUse;
3434-
if (thread_local_sv->CompareAndSwap(static_cast<void*>(sv), expected)) {
3435-
// When we see kSVInUse in the ThreadLocal, we are sure ThreadLocal
3436-
// storage has not been altered and no Scrape has happend. The
3437-
// SuperVersion is still current.
3438-
unref_sv = false;
3439-
} else {
3440-
// ThreadLocal scrape happened in the process of this GetImpl call (after
3441-
// thread local Swap() at the beginning and before CompareAndSwap()).
3442-
// This means the SuperVersion it holds is obsolete.
3443-
assert(expected == SuperVersion::kSVObsolete);
3444-
}
3389+
unref_sv = !cfd->ReturnThreadLocalSuperVersion(sv);
34453390
}
34463391

34473392
if (unref_sv) {
@@ -3678,22 +3623,18 @@ bool DBImpl::KeyMayExist(const ReadOptions& options,
36783623

36793624
Iterator* DBImpl::NewIterator(const ReadOptions& options,
36803625
ColumnFamilyHandle* column_family) {
3681-
SequenceNumber latest_snapshot = 0;
3682-
SuperVersion* super_version = nullptr;
36833626
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
36843627
auto cfd = cfh->cfd();
3685-
if (!options.tailing) {
3686-
mutex_.Lock();
3687-
super_version = cfd->GetSuperVersion()->Ref();
3688-
latest_snapshot = versions_->LastSequence();
3689-
mutex_.Unlock();
3690-
}
36913628

36923629
Iterator* iter;
36933630
if (options.tailing) {
36943631
iter = new TailingIterator(this, options, cfd);
36953632
} else {
3696-
iter = NewInternalIterator(options, cfd, super_version);
3633+
SequenceNumber latest_snapshot = versions_->LastSequence();
3634+
SuperVersion* sv = nullptr;
3635+
sv = cfd->GetReferencedSuperVersion(&mutex_);
3636+
3637+
iter = NewInternalIterator(options, cfd, sv);
36973638

36983639
auto snapshot =
36993640
options.snapshot != nullptr

0 commit comments

Comments
 (0)