2013-10-16 23:59:46 +02:00
|
|
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
|
|
|
// This source code is licensed under the BSD-style license found in the
|
|
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
//
|
2011-03-18 23:37:00 +01:00
|
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
|
2012-09-24 23:01:01 +02:00
|
|
|
#include <algorithm>
|
2014-03-13 00:40:14 +01:00
|
|
|
#include <iostream>
|
2012-11-26 22:56:45 +01:00
|
|
|
#include <set>
|
2013-11-18 06:58:16 +01:00
|
|
|
#include <unistd.h>
|
2014-09-06 00:20:05 +02:00
|
|
|
#include <thread>
|
2014-02-14 01:28:21 +01:00
|
|
|
#include <unordered_set>
|
2014-07-16 01:10:18 +02:00
|
|
|
#include <utility>
|
2012-11-26 22:56:45 +01:00
|
|
|
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
#include "db/filename.h"
|
2014-01-27 22:53:22 +01:00
|
|
|
#include "db/dbformat.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
#include "db/db_impl.h"
|
|
|
|
#include "db/filename.h"
|
2014-11-15 00:43:10 +01:00
|
|
|
#include "db/job_context.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
#include "db/version_set.h"
|
|
|
|
#include "db/write_batch_internal.h"
|
2015-02-10 02:38:32 +01:00
|
|
|
#include "port/stack_trace.h"
|
2013-08-23 17:38:13 +02:00
|
|
|
#include "rocksdb/cache.h"
|
|
|
|
#include "rocksdb/compaction_filter.h"
|
2014-01-24 01:32:49 +01:00
|
|
|
#include "rocksdb/db.h"
|
2013-08-23 17:38:13 +02:00
|
|
|
#include "rocksdb/env.h"
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
#include "rocksdb/experimental.h"
|
2014-01-24 01:32:49 +01:00
|
|
|
#include "rocksdb/filter_policy.h"
|
2013-11-18 20:32:54 +01:00
|
|
|
#include "rocksdb/perf_context.h"
|
2014-01-25 01:15:05 +01:00
|
|
|
#include "rocksdb/slice.h"
|
|
|
|
#include "rocksdb/slice_transform.h"
|
2013-10-29 01:54:09 +01:00
|
|
|
#include "rocksdb/table.h"
|
2014-07-08 21:31:49 +02:00
|
|
|
#include "rocksdb/options.h"
|
2014-02-14 01:28:21 +01:00
|
|
|
#include "rocksdb/table_properties.h"
|
2014-11-20 19:49:32 +01:00
|
|
|
#include "rocksdb/thread_status.h"
|
2014-08-19 00:19:17 +02:00
|
|
|
#include "rocksdb/utilities/write_batch_with_index.h"
|
2014-11-21 00:54:47 +01:00
|
|
|
#include "rocksdb/utilities/checkpoint.h"
|
2015-03-11 18:31:02 +01:00
|
|
|
#include "rocksdb/utilities/convenience.h"
|
2015-05-29 23:36:35 +02:00
|
|
|
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
2014-01-28 19:35:48 +01:00
|
|
|
#include "table/block_based_table_factory.h"
|
2015-03-10 20:35:15 +01:00
|
|
|
#include "table/mock_table.h"
|
2014-03-13 00:40:14 +01:00
|
|
|
#include "table/plain_table_factory.h"
|
2012-04-17 17:36:46 +02:00
|
|
|
#include "util/hash.h"
|
2014-01-24 01:32:49 +01:00
|
|
|
#include "util/hash_linklist_rep.h"
|
2014-03-13 00:40:14 +01:00
|
|
|
#include "utilities/merge_operators.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
#include "util/logging.h"
|
2015-01-09 22:04:06 +01:00
|
|
|
#include "util/compression.h"
|
2011-09-01 21:08:02 +02:00
|
|
|
#include "util/mutexlock.h"
|
2014-07-08 21:31:49 +02:00
|
|
|
#include "util/rate_limiter.h"
|
2014-01-24 01:32:49 +01:00
|
|
|
#include "util/statistics.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
#include "util/testharness.h"
|
2014-09-05 02:40:41 +02:00
|
|
|
#include "util/scoped_arena_iterator.h"
|
2014-03-24 05:49:14 +01:00
|
|
|
#include "util/sync_point.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
#include "util/testutil.h"
|
2014-10-31 23:08:10 +01:00
|
|
|
#include "util/mock_env.h"
|
2014-11-25 05:44:49 +01:00
|
|
|
#include "util/string_util.h"
|
2015-01-13 09:04:08 +01:00
|
|
|
#include "util/thread_status_util.h"
|
2015-02-02 23:49:22 +01:00
|
|
|
#include "util/xfunc.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-10-04 06:49:15 +02:00
|
|
|
namespace rocksdb {
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-12-22 12:04:45 +01:00
|
|
|
static std::string RandomString(Random* rnd, int len) {
|
2011-03-18 23:37:00 +01:00
|
|
|
std::string r;
|
|
|
|
test::RandomString(rnd, len, &r);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2012-11-06 21:02:18 +01:00
|
|
|
namespace anon {
|
2012-04-17 17:36:46 +02:00
|
|
|
class AtomicCounter {
|
|
|
|
private:
|
|
|
|
port::Mutex mu_;
|
|
|
|
int count_;
|
|
|
|
public:
|
|
|
|
AtomicCounter() : count_(0) { }
|
|
|
|
void Increment() {
|
|
|
|
MutexLock l(&mu_);
|
|
|
|
count_++;
|
|
|
|
}
|
|
|
|
int Read() {
|
|
|
|
MutexLock l(&mu_);
|
|
|
|
return count_;
|
|
|
|
}
|
|
|
|
void Reset() {
|
|
|
|
MutexLock l(&mu_);
|
|
|
|
count_ = 0;
|
|
|
|
}
|
|
|
|
};
|
2014-08-25 23:22:05 +02:00
|
|
|
|
|
|
|
struct OptionsOverride {
|
|
|
|
std::shared_ptr<const FilterPolicy> filter_policy = nullptr;
|
2015-02-02 23:49:22 +01:00
|
|
|
|
|
|
|
// Used as a bit mask of individual enums in which to skip an XF test point
|
|
|
|
int skip_policy = 0;
|
2014-08-25 23:22:05 +02:00
|
|
|
};
|
|
|
|
|
2014-07-02 18:54:20 +02:00
|
|
|
} // namespace anon
|
2013-07-28 20:53:08 +02:00
|
|
|
|
2014-07-02 18:54:20 +02:00
|
|
|
static std::string Key(int i) {
|
|
|
|
char buf[100];
|
|
|
|
snprintf(buf, sizeof(buf), "key%06d", i);
|
|
|
|
return std::string(buf);
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
|
|
|
|
2011-06-22 04:36:45 +02:00
|
|
|
// Special Env used to delay background operations
|
|
|
|
class SpecialEnv : public EnvWrapper {
|
|
|
|
public:
|
2014-10-28 22:27:26 +01:00
|
|
|
Random rnd_;
|
2015-02-05 03:03:36 +01:00
|
|
|
port::Mutex rnd_mutex_; // Lock to pretect rnd_
|
2014-10-28 22:27:26 +01:00
|
|
|
|
2013-03-01 03:04:58 +01:00
|
|
|
// sstable Sync() calls are blocked while this pointer is non-nullptr.
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> delay_sstable_sync_;
|
2011-06-22 04:36:45 +02:00
|
|
|
|
2014-09-11 02:00:00 +02:00
|
|
|
// Drop writes on the floor while this pointer is non-nullptr.
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> drop_writes_;
|
2014-09-11 02:00:00 +02:00
|
|
|
|
2013-03-01 03:04:58 +01:00
|
|
|
// Simulate no-space errors while this pointer is non-nullptr.
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> no_space_;
|
2012-01-25 23:56:52 +01:00
|
|
|
|
2013-03-01 03:04:58 +01:00
|
|
|
// Simulate non-writable file system while this pointer is non-nullptr
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> non_writable_;
|
2012-08-23 01:57:51 +02:00
|
|
|
|
2013-03-01 03:04:58 +01:00
|
|
|
// Force sync of manifest files to fail while this pointer is non-nullptr
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> manifest_sync_error_;
|
2013-01-08 21:00:13 +01:00
|
|
|
|
2013-03-01 03:04:58 +01:00
|
|
|
// Force write to manifest files to fail while this pointer is non-nullptr
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> manifest_write_error_;
|
2013-01-08 21:00:13 +01:00
|
|
|
|
2013-10-28 20:36:02 +01:00
|
|
|
// Force write to log files to fail while this pointer is non-nullptr
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> log_write_error_;
|
2013-10-28 20:36:02 +01:00
|
|
|
|
2014-11-06 19:14:47 +01:00
|
|
|
// Slow down every log write, in micro-seconds.
|
|
|
|
std::atomic<int> log_write_slowdown_;
|
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
bool count_random_reads_;
|
2012-11-06 21:02:18 +01:00
|
|
|
anon::AtomicCounter random_read_counter_;
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-04-29 19:27:58 +02:00
|
|
|
bool count_sequential_reads_;
|
|
|
|
anon::AtomicCounter sequential_read_counter_;
|
|
|
|
|
2012-11-06 21:02:18 +01:00
|
|
|
anon::AtomicCounter sleep_counter_;
|
2012-08-23 01:57:51 +02:00
|
|
|
|
2014-07-08 21:31:49 +02:00
|
|
|
std::atomic<int64_t> bytes_written_;
|
|
|
|
|
2014-09-15 20:32:01 +02:00
|
|
|
std::atomic<int> sync_counter_;
|
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
std::atomic<uint32_t> non_writeable_rate_;
|
|
|
|
|
|
|
|
std::atomic<uint32_t> new_writable_count_;
|
|
|
|
|
2014-11-07 02:07:52 +01:00
|
|
|
std::atomic<uint32_t> non_writable_count_;
|
2014-10-28 22:27:26 +01:00
|
|
|
|
2014-11-07 20:50:34 +01:00
|
|
|
std::function<void()>* table_write_callback_;
|
|
|
|
|
2015-05-16 00:52:51 +02:00
|
|
|
std::atomic<int64_t> addon_time_;
|
|
|
|
bool no_sleep_;
|
2014-12-06 01:12:10 +01:00
|
|
|
|
2015-05-16 00:52:51 +02:00
|
|
|
explicit SpecialEnv(Env* base)
|
|
|
|
: EnvWrapper(base), rnd_(301), addon_time_(0), no_sleep_(false) {
|
2014-10-27 22:50:21 +01:00
|
|
|
delay_sstable_sync_.store(false, std::memory_order_release);
|
|
|
|
drop_writes_.store(false, std::memory_order_release);
|
|
|
|
no_space_.store(false, std::memory_order_release);
|
|
|
|
non_writable_.store(false, std::memory_order_release);
|
2012-04-17 17:36:46 +02:00
|
|
|
count_random_reads_ = false;
|
2014-04-29 19:27:58 +02:00
|
|
|
count_sequential_reads_ = false;
|
2014-10-27 22:50:21 +01:00
|
|
|
manifest_sync_error_.store(false, std::memory_order_release);
|
|
|
|
manifest_write_error_.store(false, std::memory_order_release);
|
|
|
|
log_write_error_.store(false, std::memory_order_release);
|
2014-11-06 19:14:47 +01:00
|
|
|
log_write_slowdown_ = 0;
|
2014-07-08 21:31:49 +02:00
|
|
|
bytes_written_ = 0;
|
2014-09-15 20:32:01 +02:00
|
|
|
sync_counter_ = 0;
|
2014-10-28 22:27:26 +01:00
|
|
|
non_writeable_rate_ = 0;
|
|
|
|
new_writable_count_ = 0;
|
2014-11-07 02:07:52 +01:00
|
|
|
non_writable_count_ = 0;
|
2014-11-07 20:50:34 +01:00
|
|
|
table_write_callback_ = nullptr;
|
2014-07-08 21:31:49 +02:00
|
|
|
}
|
2011-06-22 04:36:45 +02:00
|
|
|
|
2013-03-15 01:00:04 +01:00
|
|
|
Status NewWritableFile(const std::string& f, unique_ptr<WritableFile>* r,
|
2015-02-26 20:28:41 +01:00
|
|
|
const EnvOptions& soptions) override {
|
2011-06-22 04:36:45 +02:00
|
|
|
class SSTableFile : public WritableFile {
|
|
|
|
private:
|
|
|
|
SpecialEnv* env_;
|
2013-01-20 11:07:13 +01:00
|
|
|
unique_ptr<WritableFile> base_;
|
2011-06-22 04:36:45 +02:00
|
|
|
|
|
|
|
public:
|
2013-01-20 11:07:13 +01:00
|
|
|
SSTableFile(SpecialEnv* env, unique_ptr<WritableFile>&& base)
|
2011-06-22 04:36:45 +02:00
|
|
|
: env_(env),
|
2013-01-20 11:07:13 +01:00
|
|
|
base_(std::move(base)) {
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
Status Append(const Slice& data) override {
|
2014-11-07 20:50:34 +01:00
|
|
|
if (env_->table_write_callback_) {
|
|
|
|
(*env_->table_write_callback_)();
|
|
|
|
}
|
2014-10-27 22:50:21 +01:00
|
|
|
if (env_->drop_writes_.load(std::memory_order_acquire)) {
|
2012-01-25 23:56:52 +01:00
|
|
|
// Drop writes on the floor
|
|
|
|
return Status::OK();
|
2014-10-27 22:50:21 +01:00
|
|
|
} else if (env_->no_space_.load(std::memory_order_acquire)) {
|
2014-09-11 02:00:00 +02:00
|
|
|
return Status::IOError("No space left on device");
|
2012-01-25 23:56:52 +01:00
|
|
|
} else {
|
2014-07-08 21:31:49 +02:00
|
|
|
env_->bytes_written_ += data.size();
|
2012-01-25 23:56:52 +01:00
|
|
|
return base_->Append(data);
|
|
|
|
}
|
|
|
|
}
|
2015-05-13 19:29:49 +02:00
|
|
|
Status Close() override {
|
|
|
|
// Check preallocation size
|
|
|
|
// preallocation size is never passed to base file.
|
|
|
|
size_t preallocation_size = preallocation_block_size();
|
|
|
|
TEST_SYNC_POINT_CALLBACK("DBTestWritableFile.GetPreallocationStatus",
|
|
|
|
&preallocation_size);
|
|
|
|
return base_->Close();
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
Status Flush() override { return base_->Flush(); }
|
|
|
|
Status Sync() override {
|
2014-09-15 20:32:01 +02:00
|
|
|
++env_->sync_counter_;
|
2014-10-27 22:50:21 +01:00
|
|
|
while (env_->delay_sstable_sync_.load(std::memory_order_acquire)) {
|
2011-06-22 04:36:45 +02:00
|
|
|
env_->SleepForMicroseconds(100000);
|
|
|
|
}
|
|
|
|
return base_->Sync();
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
void SetIOPriority(Env::IOPriority pri) override {
|
2014-07-08 21:31:49 +02:00
|
|
|
base_->SetIOPriority(pri);
|
|
|
|
}
|
2011-06-22 04:36:45 +02:00
|
|
|
};
|
2013-01-08 21:00:13 +01:00
|
|
|
class ManifestFile : public WritableFile {
|
|
|
|
private:
|
|
|
|
SpecialEnv* env_;
|
2013-01-20 11:07:13 +01:00
|
|
|
unique_ptr<WritableFile> base_;
|
2013-01-08 21:00:13 +01:00
|
|
|
public:
|
2013-01-20 11:07:13 +01:00
|
|
|
ManifestFile(SpecialEnv* env, unique_ptr<WritableFile>&& b)
|
|
|
|
: env_(env), base_(std::move(b)) { }
|
2015-02-26 20:28:41 +01:00
|
|
|
Status Append(const Slice& data) override {
|
2014-10-27 22:50:21 +01:00
|
|
|
if (env_->manifest_write_error_.load(std::memory_order_acquire)) {
|
2013-01-08 21:00:13 +01:00
|
|
|
return Status::IOError("simulated writer error");
|
|
|
|
} else {
|
|
|
|
return base_->Append(data);
|
|
|
|
}
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
Status Close() override { return base_->Close(); }
|
|
|
|
Status Flush() override { return base_->Flush(); }
|
|
|
|
Status Sync() override {
|
2014-09-15 20:32:01 +02:00
|
|
|
++env_->sync_counter_;
|
2014-10-27 22:50:21 +01:00
|
|
|
if (env_->manifest_sync_error_.load(std::memory_order_acquire)) {
|
2013-01-08 21:00:13 +01:00
|
|
|
return Status::IOError("simulated sync error");
|
|
|
|
} else {
|
|
|
|
return base_->Sync();
|
|
|
|
}
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
uint64_t GetFileSize() override { return base_->GetFileSize(); }
|
2013-01-08 21:00:13 +01:00
|
|
|
};
|
2014-11-06 20:14:28 +01:00
|
|
|
class WalFile : public WritableFile {
|
2013-10-28 20:36:02 +01:00
|
|
|
private:
|
|
|
|
SpecialEnv* env_;
|
|
|
|
unique_ptr<WritableFile> base_;
|
|
|
|
public:
|
2014-11-06 20:14:28 +01:00
|
|
|
WalFile(SpecialEnv* env, unique_ptr<WritableFile>&& b)
|
|
|
|
: env_(env), base_(std::move(b)) {}
|
2015-02-26 20:28:41 +01:00
|
|
|
Status Append(const Slice& data) override {
|
2014-10-27 22:50:21 +01:00
|
|
|
if (env_->log_write_error_.load(std::memory_order_acquire)) {
|
2013-10-28 20:36:02 +01:00
|
|
|
return Status::IOError("simulated writer error");
|
|
|
|
} else {
|
2014-11-06 19:14:47 +01:00
|
|
|
int slowdown =
|
|
|
|
env_->log_write_slowdown_.load(std::memory_order_acquire);
|
|
|
|
if (slowdown > 0) {
|
|
|
|
env_->SleepForMicroseconds(slowdown);
|
|
|
|
}
|
2013-10-28 20:36:02 +01:00
|
|
|
return base_->Append(data);
|
|
|
|
}
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
Status Close() override { return base_->Close(); }
|
|
|
|
Status Flush() override { return base_->Flush(); }
|
|
|
|
Status Sync() override {
|
2014-09-15 20:32:01 +02:00
|
|
|
++env_->sync_counter_;
|
|
|
|
return base_->Sync();
|
|
|
|
}
|
2013-10-28 20:36:02 +01:00
|
|
|
};
|
2011-06-22 04:36:45 +02:00
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
if (non_writeable_rate_.load(std::memory_order_acquire) > 0) {
|
2015-02-05 03:03:36 +01:00
|
|
|
uint32_t random_number;
|
|
|
|
{
|
|
|
|
MutexLock l(&rnd_mutex_);
|
|
|
|
random_number = rnd_.Uniform(100);
|
|
|
|
}
|
2014-10-28 22:27:26 +01:00
|
|
|
if (random_number < non_writeable_rate_.load()) {
|
|
|
|
return Status::IOError("simulated random write error");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
new_writable_count_++;
|
|
|
|
|
2014-11-07 02:07:52 +01:00
|
|
|
if (non_writable_count_.load() > 0) {
|
|
|
|
non_writable_count_--;
|
|
|
|
return Status::IOError("simulated write error");
|
2013-01-08 21:00:13 +01:00
|
|
|
}
|
2012-08-23 01:57:51 +02:00
|
|
|
|
2013-03-15 01:00:04 +01:00
|
|
|
Status s = target()->NewWritableFile(f, r, soptions);
|
2011-06-22 04:36:45 +02:00
|
|
|
if (s.ok()) {
|
2013-03-01 03:04:58 +01:00
|
|
|
if (strstr(f.c_str(), ".sst") != nullptr) {
|
2013-01-20 11:07:13 +01:00
|
|
|
r->reset(new SSTableFile(this, std::move(*r)));
|
2013-03-01 03:04:58 +01:00
|
|
|
} else if (strstr(f.c_str(), "MANIFEST") != nullptr) {
|
2013-01-20 11:07:13 +01:00
|
|
|
r->reset(new ManifestFile(this, std::move(*r)));
|
2013-10-28 20:36:02 +01:00
|
|
|
} else if (strstr(f.c_str(), "log") != nullptr) {
|
2014-11-06 20:14:28 +01:00
|
|
|
r->reset(new WalFile(this, std::move(*r)));
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2013-01-20 11:07:13 +01:00
|
|
|
Status NewRandomAccessFile(const std::string& f,
|
2013-03-15 01:00:04 +01:00
|
|
|
unique_ptr<RandomAccessFile>* r,
|
2015-02-26 20:28:41 +01:00
|
|
|
const EnvOptions& soptions) override {
|
2012-04-17 17:36:46 +02:00
|
|
|
class CountingFile : public RandomAccessFile {
|
|
|
|
private:
|
2013-01-20 11:07:13 +01:00
|
|
|
unique_ptr<RandomAccessFile> target_;
|
2012-11-06 21:02:18 +01:00
|
|
|
anon::AtomicCounter* counter_;
|
2012-04-17 17:36:46 +02:00
|
|
|
public:
|
2013-01-20 11:07:13 +01:00
|
|
|
CountingFile(unique_ptr<RandomAccessFile>&& target,
|
|
|
|
anon::AtomicCounter* counter)
|
|
|
|
: target_(std::move(target)), counter_(counter) {
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
|
|
|
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
2015-02-26 20:28:41 +01:00
|
|
|
char* scratch) const override {
|
2012-04-17 17:36:46 +02:00
|
|
|
counter_->Increment();
|
|
|
|
return target_->Read(offset, n, result, scratch);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-15 01:00:04 +01:00
|
|
|
Status s = target()->NewRandomAccessFile(f, r, soptions);
|
2012-04-17 17:36:46 +02:00
|
|
|
if (s.ok() && count_random_reads_) {
|
2013-01-20 11:07:13 +01:00
|
|
|
r->reset(new CountingFile(std::move(*r), &random_read_counter_));
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
2012-08-23 01:57:51 +02:00
|
|
|
|
2014-04-29 19:27:58 +02:00
|
|
|
Status NewSequentialFile(const std::string& f, unique_ptr<SequentialFile>* r,
|
2015-02-26 20:28:41 +01:00
|
|
|
const EnvOptions& soptions) override {
|
2014-04-29 19:27:58 +02:00
|
|
|
class CountingFile : public SequentialFile {
|
|
|
|
private:
|
|
|
|
unique_ptr<SequentialFile> target_;
|
|
|
|
anon::AtomicCounter* counter_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
CountingFile(unique_ptr<SequentialFile>&& target,
|
|
|
|
anon::AtomicCounter* counter)
|
|
|
|
: target_(std::move(target)), counter_(counter) {}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status Read(size_t n, Slice* result, char* scratch) override {
|
2014-04-29 19:27:58 +02:00
|
|
|
counter_->Increment();
|
|
|
|
return target_->Read(n, result, scratch);
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status Skip(uint64_t n) override { return target_->Skip(n); }
|
2014-04-29 19:27:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Status s = target()->NewSequentialFile(f, r, soptions);
|
|
|
|
if (s.ok() && count_sequential_reads_) {
|
|
|
|
r->reset(new CountingFile(std::move(*r), &sequential_read_counter_));
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void SleepForMicroseconds(int micros) override {
|
2012-08-23 01:57:51 +02:00
|
|
|
sleep_counter_.Increment();
|
2015-05-16 00:52:51 +02:00
|
|
|
if (no_sleep_) {
|
|
|
|
addon_time_.fetch_add(micros);
|
|
|
|
} else {
|
|
|
|
target()->SleepForMicroseconds(micros);
|
|
|
|
}
|
2012-08-23 01:57:51 +02:00
|
|
|
}
|
2014-12-06 01:12:10 +01:00
|
|
|
|
|
|
|
virtual Status GetCurrentTime(int64_t* unix_time) override {
|
|
|
|
Status s = target()->GetCurrentTime(unix_time);
|
|
|
|
if (s.ok()) {
|
2015-05-16 00:52:51 +02:00
|
|
|
*unix_time += addon_time_.load();
|
2014-12-06 01:12:10 +01:00
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
2015-03-03 19:59:36 +01:00
|
|
|
|
|
|
|
virtual uint64_t NowNanos() override {
|
2015-05-16 00:52:51 +02:00
|
|
|
return target()->NowNanos() + addon_time_.load() * 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual uint64_t NowMicros() override {
|
|
|
|
return target()->NowMicros() + addon_time_.load();
|
2015-03-03 19:59:36 +01:00
|
|
|
}
|
2011-06-22 04:36:45 +02:00
|
|
|
};
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
class DBTest : public testing::Test {
|
2013-08-23 08:10:02 +02:00
|
|
|
protected:
|
2012-04-17 17:36:46 +02:00
|
|
|
// Sequence of option configurations to try
|
|
|
|
enum OptionConfig {
|
2014-08-08 18:44:14 +02:00
|
|
|
kDefault = 0,
|
|
|
|
kBlockBasedTableWithPrefixHashIndex = 1,
|
|
|
|
kBlockBasedTableWithWholeKeyHashIndex = 2,
|
|
|
|
kPlainTableFirstBytePrefix = 3,
|
2015-01-21 20:09:56 +01:00
|
|
|
kPlainTableCappedPrefix = 4,
|
|
|
|
kPlainTableAllBytesPrefix = 5,
|
|
|
|
kVectorRep = 6,
|
|
|
|
kHashLinkList = 7,
|
|
|
|
kHashCuckoo = 8,
|
|
|
|
kMergePut = 9,
|
|
|
|
kFilter = 10,
|
|
|
|
kFullFilter = 11,
|
|
|
|
kUncompressed = 12,
|
|
|
|
kNumLevel_3 = 13,
|
|
|
|
kDBLogDir = 14,
|
|
|
|
kWalDirAndMmapReads = 15,
|
|
|
|
kManifestFileSize = 16,
|
|
|
|
kCompactOnFlush = 17,
|
|
|
|
kPerfOptions = 18,
|
|
|
|
kDeletesFilterFirst = 19,
|
|
|
|
kHashSkipList = 20,
|
|
|
|
kUniversalCompaction = 21,
|
2015-03-30 23:04:21 +02:00
|
|
|
kUniversalCompactionMultiLevel = 22,
|
|
|
|
kCompressedBlockCache = 23,
|
|
|
|
kInfiniteMaxOpenFiles = 24,
|
|
|
|
kxxHashChecksum = 25,
|
|
|
|
kFIFOCompaction = 26,
|
|
|
|
kOptimizeFiltersForHits = 27,
|
|
|
|
kEnd = 28
|
2012-04-17 17:36:46 +02:00
|
|
|
};
|
|
|
|
int option_config_;
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
public:
|
|
|
|
std::string dbname_;
|
2015-03-20 20:13:18 +01:00
|
|
|
std::string alternative_wal_dir_;
|
2014-10-31 23:08:10 +01:00
|
|
|
MockEnv* mem_env_;
|
2011-06-22 04:36:45 +02:00
|
|
|
SpecialEnv* env_;
|
2011-03-18 23:37:00 +01:00
|
|
|
DB* db_;
|
2014-02-07 23:47:16 +01:00
|
|
|
std::vector<ColumnFamilyHandle*> handles_;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
Options last_options_;
|
|
|
|
|
2013-08-02 20:46:47 +02:00
|
|
|
// Skip some options, as they may not be applicable to a specific test.
|
|
|
|
// To add more skip constants, use values 4, 8, 16, etc.
|
|
|
|
enum OptionSkip {
|
|
|
|
kNoSkip = 0,
|
|
|
|
kSkipDeletesFilterFirst = 1,
|
2013-10-18 03:33:18 +02:00
|
|
|
kSkipUniversalCompaction = 2,
|
2013-12-20 18:35:24 +01:00
|
|
|
kSkipMergePut = 4,
|
2014-04-10 23:19:43 +02:00
|
|
|
kSkipPlainTable = 8,
|
2014-04-25 21:21:34 +02:00
|
|
|
kSkipHashIndex = 16,
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
kSkipNoSeekToLast = 32,
|
2014-05-21 20:43:35 +02:00
|
|
|
kSkipHashCuckoo = 64,
|
|
|
|
kSkipFIFOCompaction = 128,
|
2014-09-17 21:31:53 +02:00
|
|
|
kSkipMmapReads = 256,
|
2013-08-02 20:46:47 +02:00
|
|
|
};
|
|
|
|
|
2014-08-25 23:22:05 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
DBTest() : option_config_(kDefault),
|
2014-10-31 23:08:10 +01:00
|
|
|
mem_env_(!getenv("MEM_ENV") ? nullptr :
|
|
|
|
new MockEnv(Env::Default())),
|
|
|
|
env_(new SpecialEnv(mem_env_ ? mem_env_ : Env::Default())) {
|
2014-11-03 23:11:33 +01:00
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
2014-10-31 23:08:10 +01:00
|
|
|
dbname_ = test::TmpDir(env_) + "/db_test";
|
2015-03-20 20:13:18 +01:00
|
|
|
alternative_wal_dir_ = dbname_ + "/wal";
|
2014-10-31 23:08:10 +01:00
|
|
|
auto options = CurrentOptions();
|
2015-03-20 20:13:18 +01:00
|
|
|
auto delete_options = options;
|
|
|
|
delete_options.wal_dir = alternative_wal_dir_;
|
|
|
|
EXPECT_OK(DestroyDB(dbname_, delete_options));
|
|
|
|
// Destroy it for not alternative WAL dir is used.
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_OK(DestroyDB(dbname_, options));
|
2013-03-01 03:04:58 +01:00
|
|
|
db_ = nullptr;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
~DBTest() {
|
2015-04-03 00:13:55 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency({});
|
|
|
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
2014-02-07 23:47:16 +01:00
|
|
|
Close();
|
2014-07-02 18:54:20 +02:00
|
|
|
Options options;
|
2014-07-15 00:34:30 +02:00
|
|
|
options.db_paths.emplace_back(dbname_, 0);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 0);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_3", 0);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_4", 0);
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_OK(DestroyDB(dbname_, options));
|
2011-06-22 04:36:45 +02:00
|
|
|
delete env_;
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Switch to a fresh database with the next option configuration to
|
|
|
|
// test. Return false if there are no more configurations to test.
|
2013-08-02 20:46:47 +02:00
|
|
|
bool ChangeOptions(int skip_mask = kNoSkip) {
|
2013-12-20 18:35:24 +01:00
|
|
|
for(option_config_++; option_config_ < kEnd; option_config_++) {
|
|
|
|
if ((skip_mask & kSkipDeletesFilterFirst) &&
|
|
|
|
option_config_ == kDeletesFilterFirst) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ((skip_mask & kSkipUniversalCompaction) &&
|
2015-03-30 23:04:21 +02:00
|
|
|
(option_config_ == kUniversalCompaction ||
|
|
|
|
option_config_ == kUniversalCompactionMultiLevel)) {
|
2013-12-20 18:35:24 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ((skip_mask & kSkipMergePut) && option_config_ == kMergePut) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-04-25 21:21:34 +02:00
|
|
|
if ((skip_mask & kSkipNoSeekToLast) &&
|
|
|
|
(option_config_ == kHashLinkList ||
|
|
|
|
option_config_ == kHashSkipList)) {;
|
|
|
|
continue;
|
|
|
|
}
|
2015-01-21 20:09:56 +01:00
|
|
|
if ((skip_mask & kSkipPlainTable) &&
|
|
|
|
(option_config_ == kPlainTableAllBytesPrefix ||
|
|
|
|
option_config_ == kPlainTableFirstBytePrefix ||
|
|
|
|
option_config_ == kPlainTableCappedPrefix)) {
|
2013-12-20 18:35:24 +01:00
|
|
|
continue;
|
|
|
|
}
|
2014-08-08 18:44:14 +02:00
|
|
|
if ((skip_mask & kSkipHashIndex) &&
|
2014-04-10 23:19:43 +02:00
|
|
|
(option_config_ == kBlockBasedTableWithPrefixHashIndex ||
|
|
|
|
option_config_ == kBlockBasedTableWithWholeKeyHashIndex)) {
|
|
|
|
continue;
|
|
|
|
}
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
if ((skip_mask & kSkipHashCuckoo) && (option_config_ == kHashCuckoo)) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-05-21 20:43:35 +02:00
|
|
|
if ((skip_mask & kSkipFIFOCompaction) &&
|
|
|
|
option_config_ == kFIFOCompaction) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-09-17 21:31:53 +02:00
|
|
|
if ((skip_mask & kSkipMmapReads) &&
|
|
|
|
option_config_ == kWalDirAndMmapReads) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-12-20 18:35:24 +01:00
|
|
|
break;
|
2013-10-18 03:33:18 +02:00
|
|
|
}
|
2013-12-20 18:35:24 +01:00
|
|
|
|
2012-08-27 08:45:35 +02:00
|
|
|
if (option_config_ >= kEnd) {
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(last_options_);
|
2012-04-17 17:36:46 +02:00
|
|
|
return false;
|
|
|
|
} else {
|
2014-10-29 19:59:18 +01:00
|
|
|
auto options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
DestroyAndReopen(options);
|
2012-04-17 17:36:46 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Switch between different compaction styles (we have only 2 now).
|
2014-10-29 21:36:18 +01:00
|
|
|
bool ChangeCompactOptions() {
|
2013-08-08 00:20:41 +02:00
|
|
|
if (option_config_ == kDefault) {
|
|
|
|
option_config_ = kUniversalCompaction;
|
2014-10-29 21:36:18 +01:00
|
|
|
Destroy(last_options_);
|
2014-10-29 19:59:18 +01:00
|
|
|
auto options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
2014-10-29 20:00:01 +01:00
|
|
|
TryReopen(options);
|
2013-08-08 00:20:41 +02:00
|
|
|
return true;
|
2015-03-30 23:04:21 +02:00
|
|
|
} else if (option_config_ == kUniversalCompaction) {
|
|
|
|
option_config_ = kUniversalCompactionMultiLevel;
|
|
|
|
Destroy(last_options_);
|
|
|
|
auto options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
TryReopen(options);
|
|
|
|
return true;
|
2013-08-08 00:20:41 +02:00
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-08 19:37:05 +02:00
|
|
|
// Switch between different filter policy
|
|
|
|
// Jump from kDefault to kFilter to kFullFilter
|
2014-10-29 21:36:18 +01:00
|
|
|
bool ChangeFilterOptions() {
|
2014-09-08 19:37:05 +02:00
|
|
|
if (option_config_ == kDefault) {
|
|
|
|
option_config_ = kFilter;
|
|
|
|
} else if (option_config_ == kFilter) {
|
|
|
|
option_config_ = kFullFilter;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2014-10-29 21:36:18 +01:00
|
|
|
Destroy(last_options_);
|
2014-10-29 19:59:18 +01:00
|
|
|
|
|
|
|
auto options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
2014-10-29 20:00:01 +01:00
|
|
|
TryReopen(options);
|
2014-10-29 19:59:18 +01:00
|
|
|
return true;
|
2014-09-08 19:37:05 +02:00
|
|
|
}
|
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Return the current option configuration.
|
2014-08-25 23:22:05 +02:00
|
|
|
Options CurrentOptions(
|
|
|
|
const anon::OptionsOverride& options_override = anon::OptionsOverride()) {
|
2012-04-17 17:36:46 +02:00
|
|
|
Options options;
|
2014-08-25 23:22:05 +02:00
|
|
|
return CurrentOptions(options, options_override);
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
}
|
|
|
|
|
2014-08-25 23:22:05 +02:00
|
|
|
Options CurrentOptions(
|
|
|
|
const Options& defaultOptions,
|
|
|
|
const anon::OptionsOverride& options_override = anon::OptionsOverride()) {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
// this redudant copy is to minimize code change w/o having lint error.
|
|
|
|
Options options = defaultOptions;
|
2015-02-02 23:49:22 +01:00
|
|
|
XFUNC_TEST("", "dbtest_options", inplace_options1, GetXFTestOptions,
|
|
|
|
reinterpret_cast<Options*>(&options),
|
|
|
|
options_override.skip_policy);
|
2014-08-25 23:22:05 +02:00
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
bool set_block_based_table_factory = true;
|
2012-04-17 17:36:46 +02:00
|
|
|
switch (option_config_) {
|
2013-12-03 21:42:15 +01:00
|
|
|
case kHashSkipList:
|
2014-03-10 20:56:46 +01:00
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
2014-07-01 00:54:31 +02:00
|
|
|
options.memtable_factory.reset(
|
|
|
|
NewHashSkipListRepFactory(16));
|
2013-08-23 08:10:02 +02:00
|
|
|
break;
|
2013-12-20 18:35:24 +01:00
|
|
|
case kPlainTableFirstBytePrefix:
|
|
|
|
options.table_factory.reset(new PlainTableFactory());
|
2014-03-10 20:56:46 +01:00
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
2013-12-20 18:35:24 +01:00
|
|
|
options.allow_mmap_reads = true;
|
|
|
|
options.max_sequential_skip_in_iterations = 999999;
|
2014-08-25 23:22:05 +02:00
|
|
|
set_block_based_table_factory = false;
|
2015-01-21 20:09:56 +01:00
|
|
|
break;
|
|
|
|
case kPlainTableCappedPrefix:
|
|
|
|
options.table_factory.reset(new PlainTableFactory());
|
|
|
|
options.prefix_extractor.reset(NewCappedPrefixTransform(8));
|
|
|
|
options.allow_mmap_reads = true;
|
|
|
|
options.max_sequential_skip_in_iterations = 999999;
|
|
|
|
set_block_based_table_factory = false;
|
2013-12-20 18:35:24 +01:00
|
|
|
break;
|
|
|
|
case kPlainTableAllBytesPrefix:
|
|
|
|
options.table_factory.reset(new PlainTableFactory());
|
2014-03-10 20:56:46 +01:00
|
|
|
options.prefix_extractor.reset(NewNoopTransform());
|
2013-12-20 18:35:24 +01:00
|
|
|
options.allow_mmap_reads = true;
|
|
|
|
options.max_sequential_skip_in_iterations = 999999;
|
2014-08-25 23:22:05 +02:00
|
|
|
set_block_based_table_factory = false;
|
2013-12-20 18:35:24 +01:00
|
|
|
break;
|
2013-03-21 23:59:47 +01:00
|
|
|
case kMergePut:
|
2013-08-20 22:35:28 +02:00
|
|
|
options.merge_operator = MergeOperators::CreatePutOperator();
|
2013-03-21 23:59:47 +01:00
|
|
|
break;
|
2012-04-17 17:36:46 +02:00
|
|
|
case kFilter:
|
2014-09-08 19:37:05 +02:00
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
|
|
|
break;
|
|
|
|
case kFullFilter:
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
2012-04-17 17:36:46 +02:00
|
|
|
break;
|
2012-08-27 08:45:35 +02:00
|
|
|
case kUncompressed:
|
2012-09-06 02:44:13 +02:00
|
|
|
options.compression = kNoCompression;
|
|
|
|
break;
|
2012-06-23 04:30:03 +02:00
|
|
|
case kNumLevel_3:
|
2012-09-06 02:44:13 +02:00
|
|
|
options.num_levels = 3;
|
|
|
|
break;
|
|
|
|
case kDBLogDir:
|
2014-10-31 23:08:10 +01:00
|
|
|
options.db_log_dir = test::TmpDir(env_);
|
2012-09-06 02:44:13 +02:00
|
|
|
break;
|
2014-09-17 21:31:53 +02:00
|
|
|
case kWalDirAndMmapReads:
|
2015-03-20 20:13:18 +01:00
|
|
|
options.wal_dir = alternative_wal_dir_;
|
2014-09-17 21:31:53 +02:00
|
|
|
// mmap reads should be orthogonal to WalDir setting, so we piggyback to
|
|
|
|
// this option config to test mmap reads as well
|
|
|
|
options.allow_mmap_reads = true;
|
2013-10-01 23:46:52 +02:00
|
|
|
break;
|
2013-01-11 02:18:50 +01:00
|
|
|
case kManifestFileSize:
|
|
|
|
options.max_manifest_file_size = 50; // 50 bytes
|
2013-02-28 23:09:30 +01:00
|
|
|
case kCompactOnFlush:
|
2013-10-04 19:21:03 +02:00
|
|
|
options.purge_redundant_kvs_while_flush =
|
|
|
|
!options.purge_redundant_kvs_while_flush;
|
2013-03-02 21:56:04 +01:00
|
|
|
break;
|
|
|
|
case kPerfOptions:
|
2015-05-16 00:52:51 +02:00
|
|
|
options.soft_rate_limit = 2.0;
|
|
|
|
options.delayed_write_rate = 8 * 1024 * 1024;
|
2013-03-02 21:56:04 +01:00
|
|
|
// TODO -- test more options
|
|
|
|
break;
|
2013-07-06 03:49:18 +02:00
|
|
|
case kDeletesFilterFirst:
|
2013-07-13 01:56:52 +02:00
|
|
|
options.filter_deletes = true;
|
2013-07-06 03:49:18 +02:00
|
|
|
break;
|
2013-08-23 08:10:02 +02:00
|
|
|
case kVectorRep:
|
2013-09-25 07:23:19 +02:00
|
|
|
options.memtable_factory.reset(new VectorRepFactory(100));
|
2013-08-23 08:10:02 +02:00
|
|
|
break;
|
2013-12-27 21:56:27 +01:00
|
|
|
case kHashLinkList:
|
2014-03-10 20:56:46 +01:00
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
2014-07-01 20:05:05 +02:00
|
|
|
options.memtable_factory.reset(
|
|
|
|
NewHashLinkListRepFactory(4, 0, 3, true, 4));
|
2013-12-27 21:56:27 +01:00
|
|
|
break;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
case kHashCuckoo:
|
|
|
|
options.memtable_factory.reset(
|
|
|
|
NewHashCuckooRepFactory(options.write_buffer_size));
|
|
|
|
break;
|
2013-08-02 20:46:47 +02:00
|
|
|
case kUniversalCompaction:
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = 1;
|
|
|
|
break;
|
|
|
|
case kUniversalCompactionMultiLevel:
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.num_levels = 8;
|
2013-08-02 20:46:47 +02:00
|
|
|
break;
|
2013-09-02 08:23:40 +02:00
|
|
|
case kCompressedBlockCache:
|
2014-03-29 00:57:04 +01:00
|
|
|
options.allow_mmap_writes = true;
|
2014-08-25 23:22:05 +02:00
|
|
|
table_options.block_cache_compressed = NewLRUCache(8*1024*1024);
|
2013-09-02 08:23:40 +02:00
|
|
|
break;
|
2014-01-07 05:29:17 +01:00
|
|
|
case kInfiniteMaxOpenFiles:
|
|
|
|
options.max_open_files = -1;
|
|
|
|
break;
|
2014-05-01 20:09:32 +02:00
|
|
|
case kxxHashChecksum: {
|
|
|
|
table_options.checksum = kxxHash;
|
|
|
|
break;
|
|
|
|
}
|
2014-05-21 20:43:35 +02:00
|
|
|
case kFIFOCompaction: {
|
|
|
|
options.compaction_style = kCompactionStyleFIFO;
|
|
|
|
break;
|
|
|
|
}
|
2014-04-10 23:19:43 +02:00
|
|
|
case kBlockBasedTableWithPrefixHashIndex: {
|
|
|
|
table_options.index_type = BlockBasedTableOptions::kHashSearch;
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kBlockBasedTableWithWholeKeyHashIndex: {
|
|
|
|
table_options.index_type = BlockBasedTableOptions::kHashSearch;
|
|
|
|
options.prefix_extractor.reset(NewNoopTransform());
|
|
|
|
break;
|
|
|
|
}
|
2015-02-17 17:03:45 +01:00
|
|
|
case kOptimizeFiltersForHits: {
|
|
|
|
options.optimize_filters_for_hits = true;
|
|
|
|
set_block_based_table_factory = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2014-08-25 23:22:05 +02:00
|
|
|
|
|
|
|
if (options_override.filter_policy) {
|
|
|
|
table_options.filter_policy = options_override.filter_policy;
|
|
|
|
}
|
|
|
|
if (set_block_based_table_factory) {
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
}
|
2014-10-31 23:08:10 +01:00
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
2012-04-17 17:36:46 +02:00
|
|
|
return options;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
DBImpl* dbfull() {
|
|
|
|
return reinterpret_cast<DBImpl*>(db_);
|
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
void CreateColumnFamilies(const std::vector<std::string>& cfs,
|
2014-10-29 20:00:01 +01:00
|
|
|
const Options& options) {
|
|
|
|
ColumnFamilyOptions cf_opts(options);
|
2014-11-11 22:47:22 +01:00
|
|
|
size_t cfi = handles_.size();
|
2014-02-07 23:47:16 +01:00
|
|
|
handles_.resize(cfi + cfs.size());
|
|
|
|
for (auto cf : cfs) {
|
|
|
|
ASSERT_OK(db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CreateAndReopenWithCF(const std::vector<std::string>& cfs,
|
2014-10-29 20:00:01 +01:00
|
|
|
const Options& options) {
|
2014-02-07 23:47:16 +01:00
|
|
|
CreateColumnFamilies(cfs, options);
|
|
|
|
std::vector<std::string> cfs_plus_default = cfs;
|
2014-04-09 18:56:17 +02:00
|
|
|
cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies(cfs_plus_default, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
}
|
|
|
|
|
2014-02-15 02:47:53 +01:00
|
|
|
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
2014-10-29 20:00:42 +01:00
|
|
|
const std::vector<Options>& options) {
|
2014-02-15 02:47:53 +01:00
|
|
|
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
|
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
2014-10-29 20:00:42 +01:00
|
|
|
const Options& options) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
|
|
|
|
}
|
|
|
|
|
|
|
|
Status TryReopenWithColumnFamilies(
|
|
|
|
const std::vector<std::string>& cfs,
|
2014-10-29 20:00:42 +01:00
|
|
|
const std::vector<Options>& options) {
|
2014-02-07 23:47:16 +01:00
|
|
|
Close();
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_EQ(cfs.size(), options.size());
|
2014-02-07 23:47:16 +01:00
|
|
|
std::vector<ColumnFamilyDescriptor> column_families;
|
|
|
|
for (size_t i = 0; i < cfs.size(); ++i) {
|
2014-10-29 20:00:42 +01:00
|
|
|
column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i]));
|
2014-02-07 23:47:16 +01:00
|
|
|
}
|
2014-10-29 20:00:42 +01:00
|
|
|
DBOptions db_opts = DBOptions(options[0]);
|
2014-02-07 23:47:16 +01:00
|
|
|
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
|
|
|
|
}
|
|
|
|
|
|
|
|
Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
2014-10-29 20:00:42 +01:00
|
|
|
const Options& options) {
|
2014-02-07 23:47:16 +01:00
|
|
|
Close();
|
2014-10-29 20:00:42 +01:00
|
|
|
std::vector<Options> v_opts(cfs.size(), options);
|
2014-02-07 23:47:16 +01:00
|
|
|
return TryReopenWithColumnFamilies(cfs, v_opts);
|
|
|
|
}
|
|
|
|
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
void Reopen(const Options& options) {
|
2014-10-29 20:00:01 +01:00
|
|
|
ASSERT_OK(TryReopen(options));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
void Close() {
|
2014-02-07 23:47:16 +01:00
|
|
|
for (auto h : handles_) {
|
|
|
|
delete h;
|
|
|
|
}
|
|
|
|
handles_.clear();
|
2012-04-17 17:36:46 +02:00
|
|
|
delete db_;
|
2013-03-01 03:04:58 +01:00
|
|
|
db_ = nullptr;
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
void DestroyAndReopen(const Options& options) {
|
2013-10-04 19:21:03 +02:00
|
|
|
//Destroy using last options
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(last_options_);
|
2014-10-29 20:00:01 +01:00
|
|
|
ASSERT_OK(TryReopen(options));
|
2013-10-04 19:21:03 +02:00
|
|
|
}
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
void Destroy(const Options& options) {
|
2014-02-07 23:47:16 +01:00
|
|
|
Close();
|
2014-10-29 19:59:18 +01:00
|
|
|
ASSERT_OK(DestroyDB(dbname_, options));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2014-10-31 23:08:10 +01:00
|
|
|
Status ReadOnlyReopen(const Options& options) {
|
|
|
|
return DB::OpenForReadOnly(options, dbname_, &db_);
|
2014-02-03 22:13:36 +01:00
|
|
|
}
|
|
|
|
|
2014-10-29 20:00:01 +01:00
|
|
|
Status TryReopen(const Options& options) {
|
2014-02-07 23:47:16 +01:00
|
|
|
Close();
|
2014-10-29 20:00:01 +01:00
|
|
|
last_options_ = options;
|
|
|
|
return DB::Open(options, dbname_, &db_);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Status Flush(int cf = 0) {
|
|
|
|
if (cf == 0) {
|
|
|
|
return db_->Flush(FlushOptions());
|
|
|
|
} else {
|
|
|
|
return db_->Flush(FlushOptions(), handles_[cf]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions()) {
|
2013-03-21 23:59:47 +01:00
|
|
|
if (kMergePut == option_config_ ) {
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
return db_->Merge(wo, k, v);
|
2013-03-21 23:59:47 +01:00
|
|
|
} else {
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
return db_->Put(wo, k, v);
|
2013-03-21 23:59:47 +01:00
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Status Put(int cf, const Slice& k, const Slice& v,
|
|
|
|
WriteOptions wo = WriteOptions()) {
|
|
|
|
if (kMergePut == option_config_) {
|
|
|
|
return db_->Merge(wo, handles_[cf], k, v);
|
|
|
|
} else {
|
|
|
|
return db_->Put(wo, handles_[cf], k, v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
Status Delete(const std::string& k) {
|
2011-04-12 21:38:58 +02:00
|
|
|
return db_->Delete(WriteOptions(), k);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Status Delete(int cf, const std::string& k) {
|
|
|
|
return db_->Delete(WriteOptions(), handles_[cf], k);
|
|
|
|
}
|
|
|
|
|
2013-03-01 03:04:58 +01:00
|
|
|
std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) {
|
2011-03-18 23:37:00 +01:00
|
|
|
ReadOptions options;
|
2013-09-02 08:23:40 +02:00
|
|
|
options.verify_checksums = true;
|
2011-03-18 23:37:00 +01:00
|
|
|
options.snapshot = snapshot;
|
|
|
|
std::string result;
|
|
|
|
Status s = db_->Get(options, k, &result);
|
|
|
|
if (s.IsNotFound()) {
|
|
|
|
result = "NOT_FOUND";
|
|
|
|
} else if (!s.ok()) {
|
|
|
|
result = s.ToString();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
std::string Get(int cf, const std::string& k,
|
|
|
|
const Snapshot* snapshot = nullptr) {
|
|
|
|
ReadOptions options;
|
|
|
|
options.verify_checksums = true;
|
|
|
|
options.snapshot = snapshot;
|
|
|
|
std::string result;
|
|
|
|
Status s = db_->Get(options, handles_[cf], k, &result);
|
|
|
|
if (s.IsNotFound()) {
|
|
|
|
result = "NOT_FOUND";
|
|
|
|
} else if (!s.ok()) {
|
|
|
|
result = s.ToString();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-12-06 01:12:10 +01:00
|
|
|
uint64_t GetNumSnapshots() {
|
|
|
|
uint64_t int_num;
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_TRUE(dbfull()->GetIntProperty("rocksdb.num-snapshots", &int_num));
|
2014-12-06 01:12:10 +01:00
|
|
|
return int_num;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t GetTimeOldestSnapshots() {
|
|
|
|
uint64_t int_num;
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_TRUE(
|
2014-12-06 01:12:10 +01:00
|
|
|
dbfull()->GetIntProperty("rocksdb.oldest-snapshot-time", &int_num));
|
|
|
|
return int_num;
|
|
|
|
}
|
|
|
|
|
2011-10-31 18:22:06 +01:00
|
|
|
// Return a string that contains all key,value pairs in order,
|
|
|
|
// formatted like "(k1->v1)(k2->v2)".
|
2014-02-07 23:47:16 +01:00
|
|
|
std::string Contents(int cf = 0) {
|
2011-10-31 18:22:06 +01:00
|
|
|
std::vector<std::string> forward;
|
|
|
|
std::string result;
|
2014-02-07 23:47:16 +01:00
|
|
|
Iterator* iter = (cf == 0) ? db_->NewIterator(ReadOptions())
|
|
|
|
: db_->NewIterator(ReadOptions(), handles_[cf]);
|
2011-10-31 18:22:06 +01:00
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
std::string s = IterStatus(iter);
|
|
|
|
result.push_back('(');
|
|
|
|
result.append(s);
|
|
|
|
result.push_back(')');
|
|
|
|
forward.push_back(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check reverse iteration results are the reverse of forward results
|
2012-11-06 21:02:18 +01:00
|
|
|
unsigned int matched = 0;
|
2011-10-31 18:22:06 +01:00
|
|
|
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_LT(matched, forward.size());
|
|
|
|
EXPECT_EQ(IterStatus(iter), forward[forward.size() - matched - 1]);
|
2011-10-31 18:22:06 +01:00
|
|
|
matched++;
|
|
|
|
}
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_EQ(matched, forward.size());
|
2011-10-31 18:22:06 +01:00
|
|
|
|
|
|
|
delete iter;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
std::string AllEntriesFor(const Slice& user_key, int cf = 0) {
|
2014-09-05 02:40:41 +02:00
|
|
|
Arena arena;
|
2014-10-23 21:03:19 +02:00
|
|
|
ScopedArenaIterator iter;
|
2014-02-07 23:47:16 +01:00
|
|
|
if (cf == 0) {
|
2014-09-05 02:40:41 +02:00
|
|
|
iter.set(dbfull()->TEST_NewInternalIterator(&arena));
|
2014-02-07 23:47:16 +01:00
|
|
|
} else {
|
2014-09-05 02:40:41 +02:00
|
|
|
iter.set(dbfull()->TEST_NewInternalIterator(&arena, handles_[cf]));
|
2014-02-07 23:47:16 +01:00
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
InternalKey target(user_key, kMaxSequenceNumber, kTypeValue);
|
|
|
|
iter->Seek(target.Encode());
|
|
|
|
std::string result;
|
|
|
|
if (!iter->status().ok()) {
|
|
|
|
result = iter->status().ToString();
|
|
|
|
} else {
|
|
|
|
result = "[ ";
|
|
|
|
bool first = true;
|
|
|
|
while (iter->Valid()) {
|
2013-11-17 22:52:55 +01:00
|
|
|
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
2011-03-18 23:37:00 +01:00
|
|
|
if (!ParseInternalKey(iter->key(), &ikey)) {
|
|
|
|
result += "CORRUPTED";
|
|
|
|
} else {
|
2012-04-17 17:36:46 +02:00
|
|
|
if (last_options_.comparator->Compare(ikey.user_key, user_key) != 0) {
|
2011-03-18 23:37:00 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!first) {
|
|
|
|
result += ", ";
|
|
|
|
}
|
|
|
|
first = false;
|
|
|
|
switch (ikey.type) {
|
|
|
|
case kTypeValue:
|
|
|
|
result += iter->value().ToString();
|
|
|
|
break;
|
2013-03-21 23:59:47 +01:00
|
|
|
case kTypeMerge:
|
|
|
|
// keep it the same as kTypeValue for testing kMergePut
|
|
|
|
result += iter->value().ToString();
|
|
|
|
break;
|
2011-03-18 23:37:00 +01:00
|
|
|
case kTypeDeletion:
|
|
|
|
result += "DEL";
|
|
|
|
break;
|
2014-01-14 16:55:16 +01:00
|
|
|
default:
|
2013-08-15 01:32:46 +02:00
|
|
|
assert(false);
|
|
|
|
break;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
iter->Next();
|
|
|
|
}
|
|
|
|
if (!first) {
|
|
|
|
result += " ";
|
|
|
|
}
|
|
|
|
result += "]";
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
int NumSortedRuns(int cf = 0) {
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
if (cf == 0) {
|
|
|
|
db_->GetColumnFamilyMetaData(&cf_meta);
|
|
|
|
} else {
|
|
|
|
db_->GetColumnFamilyMetaData(handles_[cf], &cf_meta);
|
|
|
|
}
|
|
|
|
int num_sr = static_cast<int>(cf_meta.levels[0].files.size());
|
|
|
|
for (size_t i = 1U; i < cf_meta.levels.size(); i++) {
|
|
|
|
if (cf_meta.levels[i].files.size() > 0) {
|
|
|
|
num_sr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return num_sr;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t TotalSize(int cf = 0) {
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
if (cf == 0) {
|
|
|
|
db_->GetColumnFamilyMetaData(&cf_meta);
|
|
|
|
} else {
|
|
|
|
db_->GetColumnFamilyMetaData(handles_[cf], &cf_meta);
|
|
|
|
}
|
|
|
|
return cf_meta.size;
|
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
int NumTableFilesAtLevel(int level, int cf = 0) {
|
2011-04-12 21:38:58 +02:00
|
|
|
std::string property;
|
2014-02-07 23:47:16 +01:00
|
|
|
if (cf == 0) {
|
|
|
|
// default cfd
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_TRUE(db_->GetProperty(
|
2014-02-07 23:47:16 +01:00
|
|
|
"rocksdb.num-files-at-level" + NumberToString(level), &property));
|
|
|
|
} else {
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_TRUE(db_->GetProperty(
|
2014-02-07 23:47:16 +01:00
|
|
|
handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level),
|
|
|
|
&property));
|
|
|
|
}
|
2011-04-12 21:38:58 +02:00
|
|
|
return atoi(property.c_str());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2014-10-02 01:19:16 +02:00
|
|
|
uint64_t SizeAtLevel(int level) {
|
|
|
|
std::vector<LiveFileMetaData> metadata;
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
|
|
|
uint64_t sum = 0;
|
|
|
|
for (const auto& m : metadata) {
|
|
|
|
if (m.level == level) {
|
|
|
|
sum += m.size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
int TotalLiveFiles(int cf = 0) {
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
if (cf == 0) {
|
|
|
|
db_->GetColumnFamilyMetaData(&cf_meta);
|
|
|
|
} else {
|
|
|
|
db_->GetColumnFamilyMetaData(handles_[cf], &cf_meta);
|
|
|
|
}
|
|
|
|
int num_files = 0;
|
|
|
|
for (auto& level : cf_meta.levels) {
|
|
|
|
num_files += level.files.size();
|
|
|
|
}
|
|
|
|
return num_files;
|
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
int TotalTableFiles(int cf = 0, int levels = -1) {
|
|
|
|
if (levels == -1) {
|
|
|
|
levels = CurrentOptions().num_levels;
|
|
|
|
}
|
2011-06-22 04:36:45 +02:00
|
|
|
int result = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int level = 0; level < levels; level++) {
|
|
|
|
result += NumTableFilesAtLevel(level, cf);
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2011-10-06 01:30:28 +02:00
|
|
|
// Return spread of files per level
|
2014-02-07 23:47:16 +01:00
|
|
|
std::string FilesPerLevel(int cf = 0) {
|
|
|
|
int num_levels =
|
|
|
|
(cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[1]);
|
2011-10-06 01:30:28 +02:00
|
|
|
std::string result;
|
2014-11-11 22:47:22 +01:00
|
|
|
size_t last_non_zero_offset = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int level = 0; level < num_levels; level++) {
|
|
|
|
int f = NumTableFilesAtLevel(level, cf);
|
2011-10-06 01:30:28 +02:00
|
|
|
char buf[100];
|
|
|
|
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f);
|
|
|
|
result += buf;
|
|
|
|
if (f > 0) {
|
|
|
|
last_non_zero_offset = result.size();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.resize(last_non_zero_offset);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-11-11 22:47:22 +01:00
|
|
|
size_t CountFiles() {
|
2012-01-25 23:56:52 +01:00
|
|
|
std::vector<std::string> files;
|
|
|
|
env_->GetChildren(dbname_, &files);
|
2013-10-01 23:46:52 +02:00
|
|
|
|
|
|
|
std::vector<std::string> logfiles;
|
|
|
|
if (dbname_ != last_options_.wal_dir) {
|
|
|
|
env_->GetChildren(last_options_.wal_dir, &logfiles);
|
|
|
|
}
|
|
|
|
|
2014-11-11 22:47:22 +01:00
|
|
|
return files.size() + logfiles.size();
|
2012-01-25 23:56:52 +01:00
|
|
|
}
|
|
|
|
|
2014-11-11 22:47:22 +01:00
|
|
|
size_t CountLiveFiles() {
|
2014-02-07 23:47:16 +01:00
|
|
|
std::vector<LiveFileMetaData> metadata;
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
|
|
|
return metadata.size();
|
2012-10-29 19:12:24 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
uint64_t Size(const Slice& start, const Slice& limit, int cf = 0) {
|
2011-03-18 23:37:00 +01:00
|
|
|
Range r(start, limit);
|
|
|
|
uint64_t size;
|
2014-02-07 23:47:16 +01:00
|
|
|
if (cf == 0) {
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size);
|
|
|
|
} else {
|
|
|
|
db_->GetApproximateSizes(handles_[1], &r, 1, &size);
|
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2014-12-16 06:48:16 +01:00
|
|
|
void Compact(int cf, const Slice& start, const Slice& limit,
|
|
|
|
uint32_t target_path_id) {
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.target_path_id = target_path_id;
|
|
|
|
ASSERT_OK(db_->CompactRange(compact_options, handles_[cf], &start, &limit));
|
2014-12-16 06:48:16 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
void Compact(int cf, const Slice& start, const Slice& limit) {
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(
|
|
|
|
db_->CompactRange(CompactRangeOptions(), handles_[cf], &start, &limit));
|
2014-02-07 23:47:16 +01:00
|
|
|
}
|
|
|
|
|
2011-03-22 19:32:49 +01:00
|
|
|
void Compact(const Slice& start, const Slice& limit) {
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &limit));
|
2011-10-06 01:30:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do n memtable compactions, each of which produces an sstable
|
|
|
|
// covering the range [small,large].
|
2014-02-07 23:47:16 +01:00
|
|
|
void MakeTables(int n, const std::string& small, const std::string& large,
|
|
|
|
int cf = 0) {
|
2011-10-06 01:30:28 +02:00
|
|
|
for (int i = 0; i < n; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(cf, small, "begin"));
|
|
|
|
ASSERT_OK(Put(cf, large, "end"));
|
|
|
|
ASSERT_OK(Flush(cf));
|
2011-03-22 19:32:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-22 04:36:45 +02:00
|
|
|
// Prevent pushing of new sstables into deeper levels by adding
|
|
|
|
// tables that cover a specified range to all levels.
|
2014-02-07 23:47:16 +01:00
|
|
|
void FillLevels(const std::string& smallest, const std::string& largest,
|
|
|
|
int cf) {
|
|
|
|
MakeTables(db_->NumberLevels(handles_[cf]), smallest, largest, cf);
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2011-03-22 19:32:49 +01:00
|
|
|
void DumpFileCounts(const char* label) {
|
|
|
|
fprintf(stderr, "---\n%s:\n", label);
|
|
|
|
fprintf(stderr, "maxoverlap: %lld\n",
|
|
|
|
static_cast<long long>(
|
|
|
|
dbfull()->TEST_MaxNextLevelOverlappingBytes()));
|
2012-06-23 04:30:03 +02:00
|
|
|
for (int level = 0; level < db_->NumberLevels(); level++) {
|
2011-03-22 19:32:49 +01:00
|
|
|
int num = NumTableFilesAtLevel(level);
|
|
|
|
if (num > 0) {
|
|
|
|
fprintf(stderr, " level %3d : %d files\n", level, num);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2011-10-06 01:30:28 +02:00
|
|
|
std::string DumpSSTableList() {
|
|
|
|
std::string property;
|
2013-10-05 07:32:05 +02:00
|
|
|
db_->GetProperty("rocksdb.sstables", &property);
|
2011-10-06 01:30:28 +02:00
|
|
|
return property;
|
|
|
|
}
|
|
|
|
|
2014-07-02 18:54:20 +02:00
|
|
|
int GetSstFileCount(std::string path) {
|
|
|
|
std::vector<std::string> files;
|
|
|
|
env_->GetChildren(path, &files);
|
|
|
|
|
|
|
|
int sst_count = 0;
|
|
|
|
uint64_t number;
|
|
|
|
FileType type;
|
|
|
|
for (size_t i = 0; i < files.size(); i++) {
|
|
|
|
if (ParseFileName(files[i], &number, &type) && type == kTableFile) {
|
|
|
|
sst_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sst_count;
|
|
|
|
}
|
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
// this will generate non-overlapping files since it keeps increasing key_idx
|
2015-03-17 02:49:14 +01:00
|
|
|
void GenerateNewFile(Random* rnd, int* key_idx, bool nowait = false) {
|
2014-07-02 18:54:20 +02:00
|
|
|
for (int i = 0; i < 11; i++) {
|
|
|
|
ASSERT_OK(Put(Key(*key_idx), RandomString(rnd, (i == 10) ? 1 : 10000)));
|
|
|
|
(*key_idx)++;
|
|
|
|
}
|
2015-03-17 02:49:14 +01:00
|
|
|
if (!nowait) {
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
2014-07-02 18:54:20 +02:00
|
|
|
}
|
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
void GenerateNewRandomFile(Random* rnd, bool nowait = false) {
|
|
|
|
for (int i = 0; i < 100; i++) {
|
|
|
|
ASSERT_OK(Put("key" + RandomString(rnd, 7), RandomString(rnd, 1000)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Put("key" + RandomString(rnd, 7), RandomString(rnd, 1)));
|
|
|
|
if (!nowait) {
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
std::string IterStatus(Iterator* iter) {
|
|
|
|
std::string result;
|
|
|
|
if (iter->Valid()) {
|
|
|
|
result = iter->key().ToString() + "->" + iter->value().ToString();
|
|
|
|
} else {
|
|
|
|
result = "(invalid)";
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2013-03-21 23:12:35 +01:00
|
|
|
|
|
|
|
Options OptionsForLogIterTest() {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.WAL_ttl_seconds = 1000;
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<TransactionLogIterator> OpenTransactionLogIter(
|
2013-10-25 04:09:02 +02:00
|
|
|
const SequenceNumber seq) {
|
2013-03-21 23:12:35 +01:00
|
|
|
unique_ptr<TransactionLogIterator> iter;
|
|
|
|
Status status = dbfull()->GetUpdatesSince(seq, &iter);
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_OK(status);
|
|
|
|
EXPECT_TRUE(iter->Valid());
|
2013-03-21 23:12:35 +01:00
|
|
|
return std::move(iter);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string DummyString(size_t len, char c = 'a') {
|
|
|
|
return std::string(len, c);
|
|
|
|
}
|
2013-10-18 03:33:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
void VerifyIterLast(std::string expected_key, int cf = 0) {
|
|
|
|
Iterator* iter;
|
2014-04-25 21:21:34 +02:00
|
|
|
ReadOptions ro;
|
2014-02-07 23:47:16 +01:00
|
|
|
if (cf == 0) {
|
2014-04-25 21:21:34 +02:00
|
|
|
iter = db_->NewIterator(ro);
|
2014-02-07 23:47:16 +01:00
|
|
|
} else {
|
2014-04-25 21:21:34 +02:00
|
|
|
iter = db_->NewIterator(ro, handles_[cf]);
|
2014-02-07 23:47:16 +01:00
|
|
|
}
|
2013-10-18 03:33:18 +02:00
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), expected_key);
|
|
|
|
delete iter;
|
|
|
|
}
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
|
2014-01-14 16:55:16 +01:00
|
|
|
// Used to test InplaceUpdate
|
|
|
|
|
|
|
|
// If previous value is nullptr or delta is > than previous value,
|
|
|
|
// sets newValue with delta
|
|
|
|
// If previous value is not empty,
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
// updates previous value with 'b' string of previous value size - 1.
|
|
|
|
static UpdateStatus
|
|
|
|
updateInPlaceSmallerSize(char* prevValue, uint32_t* prevSize,
|
|
|
|
Slice delta, std::string* newValue) {
|
|
|
|
if (prevValue == nullptr) {
|
2014-01-14 16:55:16 +01:00
|
|
|
*newValue = std::string(delta.size(), 'c');
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
return UpdateStatus::UPDATED;
|
2014-01-14 16:55:16 +01:00
|
|
|
} else {
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
*prevSize = *prevSize - 1;
|
|
|
|
std::string str_b = std::string(*prevSize, 'b');
|
2014-01-14 16:55:16 +01:00
|
|
|
memcpy(prevValue, str_b.c_str(), str_b.size());
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
return UpdateStatus::UPDATED_INPLACE;
|
2014-01-14 16:55:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
static UpdateStatus
|
|
|
|
updateInPlaceSmallerVarintSize(char* prevValue, uint32_t* prevSize,
|
|
|
|
Slice delta, std::string* newValue) {
|
|
|
|
if (prevValue == nullptr) {
|
|
|
|
*newValue = std::string(delta.size(), 'c');
|
|
|
|
return UpdateStatus::UPDATED;
|
|
|
|
} else {
|
|
|
|
*prevSize = 1;
|
|
|
|
std::string str_b = std::string(*prevSize, 'b');
|
|
|
|
memcpy(prevValue, str_b.c_str(), str_b.size());
|
|
|
|
return UpdateStatus::UPDATED_INPLACE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static UpdateStatus
|
|
|
|
updateInPlaceLargerSize(char* prevValue, uint32_t* prevSize,
|
|
|
|
Slice delta, std::string* newValue) {
|
|
|
|
*newValue = std::string(delta.size(), 'c');
|
|
|
|
return UpdateStatus::UPDATED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static UpdateStatus
|
|
|
|
updateInPlaceNoAction(char* prevValue, uint32_t* prevSize,
|
|
|
|
Slice delta, std::string* newValue) {
|
|
|
|
return UpdateStatus::UPDATE_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Utility method to test InplaceUpdate
|
2014-02-07 23:47:16 +01:00
|
|
|
void validateNumberOfEntries(int numValues, int cf = 0) {
|
2014-09-05 02:40:41 +02:00
|
|
|
ScopedArenaIterator iter;
|
|
|
|
Arena arena;
|
2014-02-07 23:47:16 +01:00
|
|
|
if (cf != 0) {
|
2014-09-05 02:40:41 +02:00
|
|
|
iter.set(dbfull()->TEST_NewInternalIterator(&arena, handles_[cf]));
|
2014-02-07 23:47:16 +01:00
|
|
|
} else {
|
2014-09-05 02:40:41 +02:00
|
|
|
iter.set(dbfull()->TEST_NewInternalIterator(&arena));
|
2014-02-07 23:47:16 +01:00
|
|
|
}
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(iter->status().ok(), true);
|
|
|
|
int seq = numValues;
|
|
|
|
while (iter->Valid()) {
|
|
|
|
ParsedInternalKey ikey;
|
|
|
|
ikey.sequence = -1;
|
|
|
|
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
|
|
|
|
|
|
|
// checks sequence number for updates
|
|
|
|
ASSERT_EQ(ikey.sequence, (unsigned)seq--);
|
|
|
|
iter->Next();
|
|
|
|
}
|
|
|
|
ASSERT_EQ(0, seq);
|
2014-01-14 16:55:16 +01:00
|
|
|
}
|
2014-01-24 01:32:49 +01:00
|
|
|
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
void CopyFile(const std::string& source, const std::string& destination,
|
|
|
|
uint64_t size = 0) {
|
|
|
|
const EnvOptions soptions;
|
|
|
|
unique_ptr<SequentialFile> srcfile;
|
|
|
|
ASSERT_OK(env_->NewSequentialFile(source, &srcfile, soptions));
|
|
|
|
unique_ptr<WritableFile> destfile;
|
|
|
|
ASSERT_OK(env_->NewWritableFile(destination, &destfile, soptions));
|
|
|
|
|
|
|
|
if (size == 0) {
|
|
|
|
// default argument means copy everything
|
|
|
|
ASSERT_OK(env_->GetFileSize(source, &size));
|
|
|
|
}
|
|
|
|
|
|
|
|
char buffer[4096];
|
|
|
|
Slice slice;
|
|
|
|
while (size > 0) {
|
|
|
|
uint64_t one = std::min(uint64_t(sizeof(buffer)), size);
|
|
|
|
ASSERT_OK(srcfile->Read(one, &slice, buffer));
|
|
|
|
ASSERT_OK(destfile->Append(slice));
|
|
|
|
size -= slice.size();
|
|
|
|
}
|
|
|
|
ASSERT_OK(destfile->Close());
|
|
|
|
}
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
};
|
|
|
|
|
2014-01-17 21:46:06 +01:00
|
|
|
static long TestGetTickerCount(const Options& options, Tickers ticker_type) {
|
|
|
|
return options.statistics->getTickerCount(ticker_type);
|
|
|
|
}
|
|
|
|
|
2014-02-14 01:28:21 +01:00
|
|
|
// A helper function that ensures the table properties returned in
|
|
|
|
// `GetPropertiesOfAllTablesTest` is correct.
|
2015-04-25 11:14:27 +02:00
|
|
|
// This test assumes entries size is different for each of the tables.
|
2014-04-10 06:17:14 +02:00
|
|
|
namespace {
|
2014-02-14 01:28:21 +01:00
|
|
|
void VerifyTableProperties(DB* db, uint64_t expected_entries_size) {
|
|
|
|
TablePropertiesCollection props;
|
|
|
|
ASSERT_OK(db->GetPropertiesOfAllTables(&props));
|
|
|
|
|
2014-02-15 01:18:55 +01:00
|
|
|
ASSERT_EQ(4U, props.size());
|
2014-02-14 01:28:21 +01:00
|
|
|
std::unordered_set<uint64_t> unique_entries;
|
|
|
|
|
|
|
|
// Indirect test
|
|
|
|
uint64_t sum = 0;
|
|
|
|
for (const auto& item : props) {
|
|
|
|
unique_entries.insert(item.second->num_entries);
|
|
|
|
sum += item.second->num_entries;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(props.size(), unique_entries.size());
|
|
|
|
ASSERT_EQ(expected_entries_size, sum);
|
|
|
|
}
|
2014-09-11 03:46:09 +02:00
|
|
|
|
|
|
|
uint64_t GetNumberOfSstFilesForColumnFamily(DB* db,
|
|
|
|
std::string column_family_name) {
|
|
|
|
std::vector<LiveFileMetaData> metadata;
|
|
|
|
db->GetLiveFilesMetaData(&metadata);
|
|
|
|
uint64_t result = 0;
|
|
|
|
for (auto& fileMetadata : metadata) {
|
|
|
|
result += (fileMetadata.column_family_name == column_family_name);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2014-04-10 06:17:14 +02:00
|
|
|
} // namespace
|
2014-03-13 00:40:14 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, Empty) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-12-27 21:23:17 +01:00
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-04-23 02:17:33 +02:00
|
|
|
std::string num;
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &num));
|
|
|
|
ASSERT_EQ("0", num);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
2014-04-23 02:17:33 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &num));
|
|
|
|
ASSERT_EQ("1", num);
|
2013-12-27 21:23:17 +01:00
|
|
|
|
2014-10-27 22:50:21 +01:00
|
|
|
// Block sync calls
|
|
|
|
env_->delay_sstable_sync_.store(true, std::memory_order_release);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "k1", std::string(100000, 'x')); // Fill memtable
|
2014-04-23 02:17:33 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &num));
|
|
|
|
ASSERT_EQ("2", num);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "k2", std::string(100000, 'y')); // Trigger compaction
|
2014-04-23 02:17:33 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &num));
|
|
|
|
ASSERT_EQ("1", num);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
2014-10-27 22:50:21 +01:00
|
|
|
// Release sync calls
|
|
|
|
env_->delay_sstable_sync_.store(false, std::memory_order_release);
|
2014-08-27 01:26:29 +02:00
|
|
|
|
|
|
|
ASSERT_OK(db_->DisableFileDeletions());
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
|
|
|
ASSERT_EQ("1", num);
|
|
|
|
|
|
|
|
ASSERT_OK(db_->DisableFileDeletions());
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
|
|
|
ASSERT_EQ("2", num);
|
|
|
|
|
|
|
|
ASSERT_OK(db_->DisableFileDeletions());
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
|
|
|
ASSERT_EQ("3", num);
|
|
|
|
|
|
|
|
ASSERT_OK(db_->EnableFileDeletions(false));
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
|
|
|
ASSERT_EQ("2", num);
|
|
|
|
|
|
|
|
ASSERT_OK(db_->EnableFileDeletions());
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
|
|
|
ASSERT_EQ("0", num);
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, WriteEmptyBatch) {
|
2014-11-06 03:07:22 +01:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
ASSERT_OK(Put(1, "foo", "bar"));
|
|
|
|
env_->sync_counter_.store(0);
|
|
|
|
WriteOptions wo;
|
|
|
|
wo.sync = true;
|
|
|
|
wo.disableWAL = false;
|
|
|
|
WriteBatch empty_batch;
|
|
|
|
ASSERT_OK(dbfull()->Write(wo, &empty_batch));
|
|
|
|
ASSERT_GE(env_->sync_counter_.load(), 1);
|
|
|
|
|
|
|
|
// make sure we can re-open it.
|
|
|
|
ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options));
|
|
|
|
ASSERT_EQ("bar", Get(1, "foo"));
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ReadOnlyDB) {
|
2014-02-03 22:13:36 +01:00
|
|
|
ASSERT_OK(Put("foo", "v1"));
|
|
|
|
ASSERT_OK(Put("bar", "v2"));
|
|
|
|
ASSERT_OK(Put("foo", "v3"));
|
|
|
|
Close();
|
|
|
|
|
2014-10-31 23:08:10 +01:00
|
|
|
auto options = CurrentOptions();
|
|
|
|
assert(options.env = env_);
|
|
|
|
ASSERT_OK(ReadOnlyReopen(options));
|
2014-02-03 22:13:36 +01:00
|
|
|
ASSERT_EQ("v3", Get("foo"));
|
|
|
|
ASSERT_EQ("v2", Get("bar"));
|
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions());
|
|
|
|
int count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 2);
|
|
|
|
delete iter;
|
2014-08-27 02:19:03 +02:00
|
|
|
Close();
|
|
|
|
|
|
|
|
// Reopen and flush memtable.
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-08-27 02:19:03 +02:00
|
|
|
Flush();
|
|
|
|
Close();
|
|
|
|
// Now check keys in read only mode.
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(ReadOnlyReopen(options));
|
2014-08-27 02:19:03 +02:00
|
|
|
ASSERT_EQ("v3", Get("foo"));
|
|
|
|
ASSERT_EQ("v2", Get("bar"));
|
2014-02-03 22:13:36 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactedDB) {
|
2014-09-25 20:14:01 +02:00
|
|
|
const uint64_t kFileSize = 1 << 20;
|
|
|
|
Options options;
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.max_mem_compaction_level = 0;
|
|
|
|
options.write_buffer_size = kFileSize;
|
|
|
|
options.target_file_size_base = kFileSize;
|
|
|
|
options.max_bytes_for_level_base = 1 << 30;
|
|
|
|
options.compression = kNoCompression;
|
2014-10-31 23:08:10 +01:00
|
|
|
options = CurrentOptions(options);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-09-25 20:14:01 +02:00
|
|
|
// 1 L0 file, use CompactedDB if max_open_files = -1
|
|
|
|
ASSERT_OK(Put("aaa", DummyString(kFileSize / 2, '1')));
|
|
|
|
Flush();
|
|
|
|
Close();
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(ReadOnlyReopen(options));
|
2014-09-25 20:14:01 +02:00
|
|
|
Status s = Put("new", "value");
|
|
|
|
ASSERT_EQ(s.ToString(),
|
|
|
|
"Not implemented: Not supported operation in read only mode.");
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, '1'), Get("aaa"));
|
|
|
|
Close();
|
|
|
|
options.max_open_files = -1;
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(ReadOnlyReopen(options));
|
2014-09-25 20:14:01 +02:00
|
|
|
s = Put("new", "value");
|
|
|
|
ASSERT_EQ(s.ToString(),
|
|
|
|
"Not implemented: Not supported in compacted db mode.");
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, '1'), Get("aaa"));
|
|
|
|
Close();
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-09-25 20:14:01 +02:00
|
|
|
// Add more L0 files
|
|
|
|
ASSERT_OK(Put("bbb", DummyString(kFileSize / 2, '2')));
|
|
|
|
Flush();
|
|
|
|
ASSERT_OK(Put("aaa", DummyString(kFileSize / 2, 'a')));
|
|
|
|
Flush();
|
|
|
|
ASSERT_OK(Put("bbb", DummyString(kFileSize / 2, 'b')));
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
ASSERT_OK(Put("eee", DummyString(kFileSize / 2, 'e')));
|
2014-09-25 20:14:01 +02:00
|
|
|
Flush();
|
|
|
|
Close();
|
|
|
|
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(ReadOnlyReopen(options));
|
2014-09-25 20:14:01 +02:00
|
|
|
// Fallback to read-only DB
|
|
|
|
s = Put("new", "value");
|
|
|
|
ASSERT_EQ(s.ToString(),
|
|
|
|
"Not implemented: Not supported operation in read only mode.");
|
|
|
|
Close();
|
|
|
|
|
|
|
|
// Full compaction
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-09-25 20:14:01 +02:00
|
|
|
// Add more keys
|
|
|
|
ASSERT_OK(Put("fff", DummyString(kFileSize / 2, 'f')));
|
|
|
|
ASSERT_OK(Put("hhh", DummyString(kFileSize / 2, 'h')));
|
|
|
|
ASSERT_OK(Put("iii", DummyString(kFileSize / 2, 'i')));
|
|
|
|
ASSERT_OK(Put("jjj", DummyString(kFileSize / 2, 'j')));
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-09-25 20:14:01 +02:00
|
|
|
ASSERT_EQ(3, NumTableFilesAtLevel(1));
|
|
|
|
Close();
|
|
|
|
|
|
|
|
// CompactedDB
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(ReadOnlyReopen(options));
|
2014-09-25 20:14:01 +02:00
|
|
|
s = Put("new", "value");
|
|
|
|
ASSERT_EQ(s.ToString(),
|
|
|
|
"Not implemented: Not supported in compacted db mode.");
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("abc"));
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'a'), Get("aaa"));
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'b'), Get("bbb"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("ccc"));
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'e'), Get("eee"));
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'f'), Get("fff"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("ggg"));
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'h'), Get("hhh"));
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'i'), Get("iii"));
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'j'), Get("jjj"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("kkk"));
|
2014-09-25 22:34:51 +02:00
|
|
|
|
|
|
|
// MultiGet
|
|
|
|
std::vector<std::string> values;
|
|
|
|
std::vector<Status> status_list = dbfull()->MultiGet(ReadOptions(),
|
|
|
|
std::vector<Slice>({Slice("aaa"), Slice("ccc"), Slice("eee"),
|
|
|
|
Slice("ggg"), Slice("iii"), Slice("kkk")}),
|
|
|
|
&values);
|
2014-09-29 23:52:16 +02:00
|
|
|
ASSERT_EQ(status_list.size(), static_cast<uint64_t>(6));
|
|
|
|
ASSERT_EQ(values.size(), static_cast<uint64_t>(6));
|
2014-09-25 22:34:51 +02:00
|
|
|
ASSERT_OK(status_list[0]);
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'a'), values[0]);
|
|
|
|
ASSERT_TRUE(status_list[1].IsNotFound());
|
|
|
|
ASSERT_OK(status_list[2]);
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'e'), values[2]);
|
|
|
|
ASSERT_TRUE(status_list[3].IsNotFound());
|
|
|
|
ASSERT_OK(status_list[4]);
|
|
|
|
ASSERT_EQ(DummyString(kFileSize / 2, 'i'), values[4]);
|
|
|
|
ASSERT_TRUE(status_list[5].IsNotFound());
|
2014-09-25 20:14:01 +02:00
|
|
|
}
|
|
|
|
|
2013-11-13 07:46:51 +01:00
|
|
|
// Make sure that when options.block_cache is set, after a new table is
|
|
|
|
// created its index/filter blocks are added to block cache.
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IndexAndFilterBlocksOfNewTableAddedToCache) {
|
2013-11-13 07:46:51 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
2014-01-24 19:57:15 +01:00
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.cache_index_and_filter_blocks = true;
|
2014-08-25 23:22:05 +02:00
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
2014-01-24 19:57:15 +01:00
|
|
|
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-11-13 07:46:51 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", "val"));
|
|
|
|
// Create a new table.
|
|
|
|
ASSERT_OK(Flush(1));
|
2013-11-13 07:46:51 +01:00
|
|
|
|
|
|
|
// index/filter blocks added to block cache right after table creation.
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
|
|
|
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
2013-11-13 07:46:51 +01:00
|
|
|
ASSERT_EQ(2, /* only index/filter were added */
|
2014-01-17 21:46:06 +01:00
|
|
|
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
|
|
|
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
|
2014-08-05 20:27:34 +02:00
|
|
|
uint64_t int_num;
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 0U);
|
2013-11-13 07:46:51 +01:00
|
|
|
|
|
|
|
// Make sure filter block is in cache.
|
|
|
|
std::string value;
|
|
|
|
ReadOptions ropt;
|
2014-02-07 23:47:16 +01:00
|
|
|
db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
|
2013-11-13 07:46:51 +01:00
|
|
|
|
|
|
|
// Miss count should remain the same.
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
|
|
|
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
2013-11-13 07:46:51 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
|
|
|
ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
2013-11-13 07:46:51 +01:00
|
|
|
|
|
|
|
// Make sure index block is in cache.
|
2014-01-17 21:46:06 +01:00
|
|
|
auto index_block_hit = TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT);
|
2014-02-07 23:47:16 +01:00
|
|
|
value = Get(1, "key");
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
2013-11-13 07:46:51 +01:00
|
|
|
ASSERT_EQ(index_block_hit + 1,
|
2014-01-17 21:46:06 +01:00
|
|
|
TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
2013-11-13 07:46:51 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
value = Get(1, "key");
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
2013-11-13 07:46:51 +01:00
|
|
|
ASSERT_EQ(index_block_hit + 2,
|
2014-01-17 21:46:06 +01:00
|
|
|
TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
2013-11-13 07:46:51 +01:00
|
|
|
}
|
|
|
|
|
2015-04-18 00:26:50 +02:00
|
|
|
TEST_F(DBTest, ParanoidFileChecks) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.paranoid_file_checks = true;
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.cache_index_and_filter_blocks = false;
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
|
|
|
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
ASSERT_OK(Put(1, "1_key", "val"));
|
|
|
|
ASSERT_OK(Put(1, "9_key", "val"));
|
|
|
|
// Create a new table.
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(1, /* read and cache data block */
|
|
|
|
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
|
|
|
|
|
|
|
ASSERT_OK(Put(1, "1_key2", "val2"));
|
|
|
|
ASSERT_OK(Put(1, "9_key2", "val2"));
|
|
|
|
// Create a new SST file. This will further trigger a compaction
|
|
|
|
// and generate another file.
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
|
|
|
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
|
|
|
|
|
|
|
// After disabling options.paranoid_file_checks. NO further block
|
|
|
|
// is added after generating a new file.
|
|
|
|
ASSERT_OK(
|
|
|
|
dbfull()->SetOptions(handles_[1], {{"paranoid_file_checks", "false"}}));
|
|
|
|
|
|
|
|
ASSERT_OK(Put(1, "1_key3", "val3"));
|
|
|
|
ASSERT_OK(Put(1, "9_key3", "val3"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_OK(Put(1, "1_key4", "val4"));
|
|
|
|
ASSERT_OK(Put(1, "9_key4", "val4"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
|
|
|
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetPropertiesOfAllTablesTest) {
|
2014-02-14 01:28:21 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-09-18 22:32:44 +02:00
|
|
|
options.max_background_flushes = 0;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-02-14 01:28:21 +01:00
|
|
|
// Create 4 tables
|
|
|
|
for (int table = 0; table < 4; ++table) {
|
|
|
|
for (int i = 0; i < 10 + table; ++i) {
|
2014-11-25 05:44:49 +01:00
|
|
|
db_->Put(WriteOptions(), ToString(table * 100 + i), "val");
|
2014-02-14 01:28:21 +01:00
|
|
|
}
|
|
|
|
db_->Flush(FlushOptions());
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. Read table properties directly from file
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-02-14 01:28:21 +01:00
|
|
|
VerifyTableProperties(db_, 10 + 11 + 12 + 13);
|
|
|
|
|
|
|
|
// 2. Put two tables to table cache and
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-02-14 01:28:21 +01:00
|
|
|
// fetch key from 1st and 2nd table, which will internally place that table to
|
|
|
|
// the table cache.
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
2014-11-25 05:44:49 +01:00
|
|
|
Get(ToString(i * 100 + 0));
|
2014-02-14 01:28:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
VerifyTableProperties(db_, 10 + 11 + 12 + 13);
|
|
|
|
|
|
|
|
// 3. Put all tables to table cache
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-02-14 01:28:21 +01:00
|
|
|
// fetch key from 1st and 2nd table, which will internally place that table to
|
|
|
|
// the table cache.
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
2014-11-25 05:44:49 +01:00
|
|
|
Get(ToString(i * 100 + 0));
|
2014-02-14 01:28:21 +01:00
|
|
|
}
|
|
|
|
VerifyTableProperties(db_, 10 + 11 + 12 + 13);
|
|
|
|
}
|
|
|
|
|
A new call back to TablePropertiesCollector to allow users know the entry is add, delete or merge
Summary:
Currently users have no idea a key is add, delete or merge from TablePropertiesCollector call back. Add a new function to add it.
Also refactor the codes so that
(1) make table property collector and internal table property collector two separate data structures with the later one now exposed
(2) table builders only receive internal table properties
Test Plan: Add cases in table_properties_collector_test to cover both of old and new ways of using TablePropertiesCollector.
Reviewers: yhchiang, igor.sugak, rven, igor
Reviewed By: rven, igor
Subscribers: meyering, yoshinorim, maykov, leveldb, dhruba
Differential Revision: https://reviews.facebook.net/D35373
2015-04-06 19:04:30 +02:00
|
|
|
class CoutingUserTblPropCollector : public TablePropertiesCollector {
|
|
|
|
public:
|
|
|
|
const char* Name() const override { return "CoutingUserTblPropCollector"; }
|
|
|
|
|
|
|
|
Status Finish(UserCollectedProperties* properties) override {
|
|
|
|
std::string encoded;
|
|
|
|
PutVarint32(&encoded, count_);
|
|
|
|
*properties = UserCollectedProperties{
|
|
|
|
{"CoutingUserTblPropCollector", message_}, {"Count", encoded},
|
|
|
|
};
|
|
|
|
return Status::OK();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status AddUserKey(const Slice& user_key, const Slice& value, EntryType type,
|
|
|
|
SequenceNumber seq, uint64_t file_size) override {
|
|
|
|
++count_;
|
|
|
|
return Status::OK();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual UserCollectedProperties GetReadableProperties() const override {
|
|
|
|
return UserCollectedProperties{};
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string message_ = "Rocksdb";
|
|
|
|
uint32_t count_ = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
class CoutingUserTblPropCollectorFactory
|
|
|
|
: public TablePropertiesCollectorFactory {
|
|
|
|
public:
|
|
|
|
virtual TablePropertiesCollector* CreateTablePropertiesCollector() override {
|
|
|
|
return new CoutingUserTblPropCollector();
|
|
|
|
}
|
|
|
|
const char* Name() const override {
|
|
|
|
return "CoutingUserTblPropCollectorFactory";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(DBTest, GetUserDefinedTablaProperties) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
options.table_properties_collector_factories.resize(1);
|
|
|
|
options.table_properties_collector_factories[0] =
|
|
|
|
std::make_shared<CoutingUserTblPropCollectorFactory>();
|
|
|
|
Reopen(options);
|
|
|
|
// Create 4 tables
|
|
|
|
for (int table = 0; table < 4; ++table) {
|
|
|
|
for (int i = 0; i < 10 + table; ++i) {
|
|
|
|
db_->Put(WriteOptions(), ToString(table * 100 + i), "val");
|
|
|
|
}
|
|
|
|
db_->Flush(FlushOptions());
|
|
|
|
}
|
|
|
|
|
|
|
|
TablePropertiesCollection props;
|
|
|
|
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
|
|
|
|
ASSERT_EQ(4U, props.size());
|
|
|
|
uint32_t sum = 0;
|
|
|
|
for (const auto& item : props) {
|
|
|
|
auto& user_collected = item.second->user_collected_properties;
|
|
|
|
ASSERT_TRUE(user_collected.find("CoutingUserTblPropCollector") !=
|
|
|
|
user_collected.end());
|
|
|
|
ASSERT_EQ(user_collected.at("CoutingUserTblPropCollector"), "Rocksdb");
|
|
|
|
ASSERT_TRUE(user_collected.find("Count") != user_collected.end());
|
|
|
|
Slice key(user_collected.at("Count"));
|
|
|
|
uint32_t count;
|
|
|
|
ASSERT_TRUE(GetVarint32(&key, &count));
|
|
|
|
sum += count;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(10u + 11u + 12u + 13u, sum);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, LevelLimitReopen) {
|
2013-01-24 19:54:26 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-01-24 19:54:26 +01:00
|
|
|
|
|
|
|
const std::string value(1024 * 1024, ' ');
|
|
|
|
int i = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
while (NumTableFilesAtLevel(2, 1) == 0) {
|
|
|
|
ASSERT_OK(Put(1, Key(i++), value));
|
2013-01-24 19:54:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
options.num_levels = 1;
|
2013-05-23 19:56:36 +02:00
|
|
|
options.max_bytes_for_level_multiplier_additional.resize(1, 1);
|
2014-10-29 20:00:42 +01:00
|
|
|
Status s = TryReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-01-15 00:27:09 +01:00
|
|
|
ASSERT_EQ(s.IsInvalidArgument(), true);
|
2013-01-24 19:54:26 +01:00
|
|
|
ASSERT_EQ(s.ToString(),
|
2014-01-15 00:27:09 +01:00
|
|
|
"Invalid argument: db has more levels than options.num_levels");
|
2013-01-24 19:54:26 +01:00
|
|
|
|
|
|
|
options.num_levels = 10;
|
2013-05-23 19:56:36 +02:00
|
|
|
options.max_bytes_for_level_multiplier_additional.resize(10, 1);
|
2014-10-29 20:00:42 +01:00
|
|
|
ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options));
|
2013-01-24 19:54:26 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PutDeleteGet) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v2"));
|
|
|
|
ASSERT_EQ("v2", Get(1, "foo"));
|
|
|
|
ASSERT_OK(Delete(1, "foo"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, "foo"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetFromImmutableLayer) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2012-04-17 17:36:46 +02:00
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-06-22 04:36:45 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
2011-06-22 04:36:45 +02:00
|
|
|
|
2014-10-27 22:50:21 +01:00
|
|
|
// Block sync calls
|
|
|
|
env_->delay_sstable_sync_.store(true, std::memory_order_release);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "k1", std::string(100000, 'x')); // Fill memtable
|
|
|
|
Put(1, "k2", std::string(100000, 'y')); // Trigger flush
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(0, "foo"));
|
2014-10-27 22:50:21 +01:00
|
|
|
// Release sync calls
|
|
|
|
env_->delay_sstable_sync_.store(false, std::memory_order_release);
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetFromVersions) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(0, "foo"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetSnapshot) {
|
2015-02-02 23:49:22 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2015-02-02 23:49:22 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override));
|
2012-04-17 17:36:46 +02:00
|
|
|
// Try with both a short key and a long key
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
|
|
std::string key = (i == 0) ? std::string("foo") : std::string(200, 'x');
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, key, "v1"));
|
2012-04-17 17:36:46 +02:00
|
|
|
const Snapshot* s1 = db_->GetSnapshot();
|
2014-12-11 03:39:09 +01:00
|
|
|
if (option_config_ == kHashCuckoo) {
|
|
|
|
// NOt supported case.
|
|
|
|
ASSERT_TRUE(s1 == nullptr);
|
|
|
|
break;
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, key, "v2"));
|
|
|
|
ASSERT_EQ("v2", Get(1, key));
|
|
|
|
ASSERT_EQ("v1", Get(1, key, s1));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ("v2", Get(1, key));
|
|
|
|
ASSERT_EQ("v1", Get(1, key, s1));
|
2012-04-17 17:36:46 +02:00
|
|
|
db_->ReleaseSnapshot(s1);
|
|
|
|
}
|
2014-12-11 03:39:09 +01:00
|
|
|
} while (ChangeOptions());
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetSnapshotLink) {
|
2014-11-14 20:38:26 +01:00
|
|
|
do {
|
|
|
|
Options options;
|
|
|
|
const std::string snapshot_name = test::TmpDir(env_) + "/snapshot";
|
|
|
|
DB* snapshotDB;
|
|
|
|
ReadOptions roptions;
|
|
|
|
std::string result;
|
2014-11-21 00:54:47 +01:00
|
|
|
Checkpoint* checkpoint;
|
2014-11-14 20:38:26 +01:00
|
|
|
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
delete db_;
|
|
|
|
db_ = nullptr;
|
|
|
|
ASSERT_OK(DestroyDB(dbname_, options));
|
|
|
|
ASSERT_OK(DestroyDB(snapshot_name, options));
|
|
|
|
env_->DeleteDir(snapshot_name);
|
|
|
|
|
|
|
|
// Create a database
|
|
|
|
Status s;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
|
|
|
std::string key = std::string("foo");
|
|
|
|
ASSERT_OK(Put(key, "v1"));
|
|
|
|
// Take a snapshot
|
2014-11-21 00:54:47 +01:00
|
|
|
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
|
|
|
|
ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name));
|
2014-11-14 20:38:26 +01:00
|
|
|
ASSERT_OK(Put(key, "v2"));
|
|
|
|
ASSERT_EQ("v2", Get(key));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
ASSERT_EQ("v2", Get(key));
|
|
|
|
// Open snapshot and verify contents while DB is running
|
|
|
|
options.create_if_missing = false;
|
|
|
|
ASSERT_OK(DB::Open(options, snapshot_name, &snapshotDB));
|
|
|
|
ASSERT_OK(snapshotDB->Get(roptions, key, &result));
|
|
|
|
ASSERT_EQ("v1", result);
|
|
|
|
delete snapshotDB;
|
|
|
|
snapshotDB = nullptr;
|
|
|
|
delete db_;
|
|
|
|
db_ = nullptr;
|
|
|
|
|
|
|
|
// Destroy original DB
|
|
|
|
ASSERT_OK(DestroyDB(dbname_, options));
|
|
|
|
|
|
|
|
// Open snapshot and verify contents
|
|
|
|
options.create_if_missing = false;
|
|
|
|
dbname_ = snapshot_name;
|
|
|
|
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
|
|
|
ASSERT_EQ("v1", Get(key));
|
|
|
|
delete db_;
|
|
|
|
db_ = nullptr;
|
|
|
|
ASSERT_OK(DestroyDB(dbname_, options));
|
2014-11-24 19:20:50 +01:00
|
|
|
delete checkpoint;
|
2014-11-14 20:38:26 +01:00
|
|
|
|
|
|
|
// Restore DB name
|
|
|
|
dbname_ = test::TmpDir(env_) + "/db_test";
|
|
|
|
} while (ChangeOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetLevel0Ordering) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2012-04-17 17:36:46 +02:00
|
|
|
// Check that we process level-0 files in correct order. The code
|
|
|
|
// below generates two level-0 files where the earlier one comes
|
|
|
|
// before the later one in the level-0 file list since the earlier
|
|
|
|
// one has a smaller "smallest" key.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "bar", "b"));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v2"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ("v2", Get(1, "foo"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, WrongLevel0Config) {
|
2015-02-24 01:08:27 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
Close();
|
|
|
|
ASSERT_OK(DestroyDB(dbname_, options));
|
|
|
|
options.level0_stop_writes_trigger = 1;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
|
|
|
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetOrderedByLevels) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
Compact(1, "a", "z");
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v2"));
|
|
|
|
ASSERT_EQ("v2", Get(1, "foo"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ("v2", Get(1, "foo"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetPicksCorrectFile) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2012-04-17 17:36:46 +02:00
|
|
|
// Arrange to have multiple files in a non-level-0 level.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "va"));
|
|
|
|
Compact(1, "a", "b");
|
|
|
|
ASSERT_OK(Put(1, "x", "vx"));
|
|
|
|
Compact(1, "x", "y");
|
|
|
|
ASSERT_OK(Put(1, "f", "vf"));
|
|
|
|
Compact(1, "f", "g");
|
|
|
|
ASSERT_EQ("va", Get(1, "a"));
|
|
|
|
ASSERT_EQ("vf", Get(1, "f"));
|
|
|
|
ASSERT_EQ("vx", Get(1, "x"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetEncountersEmptyLevel) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-09-18 22:32:44 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
options.disableDataSync = true;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-04-17 17:36:46 +02:00
|
|
|
// Arrange for the following to happen:
|
|
|
|
// * sstable A in level 0
|
|
|
|
// * nothing in level 1
|
|
|
|
// * sstable B in level 2
|
|
|
|
// Then do enough Get() calls to arrange for an automatic compaction
|
|
|
|
// of sstable A. A bug would cause the compaction to be marked as
|
2015-04-25 11:14:27 +02:00
|
|
|
// occurring at level 1 (instead of the correct level 0).
|
2012-04-17 17:36:46 +02:00
|
|
|
|
|
|
|
// Step 1: First place sstables in levels 0 and 2
|
|
|
|
int compaction_count = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
while (NumTableFilesAtLevel(0, 1) == 0 || NumTableFilesAtLevel(2, 1) == 0) {
|
2012-04-17 17:36:46 +02:00
|
|
|
ASSERT_LE(compaction_count, 100) << "could not fill levels 0 and 2";
|
|
|
|
compaction_count++;
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "a", "begin");
|
|
|
|
Put(1, "z", "end");
|
|
|
|
ASSERT_OK(Flush(1));
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
2011-09-01 21:08:02 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Step 2: clear level 1 if necessary.
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2, 1), 1);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2012-08-27 08:45:35 +02:00
|
|
|
// Step 3: read a bunch of times
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, "missing"));
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
2012-08-27 08:45:35 +02:00
|
|
|
|
|
|
|
// Step 4: Wait for compaction to finish
|
|
|
|
env_->SleepForMicroseconds(1000000);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 1); // XXX
|
2014-05-21 20:43:35 +02:00
|
|
|
} while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction));
|
2011-09-01 21:08:02 +02:00
|
|
|
}
|
|
|
|
|
2013-07-26 21:57:01 +02:00
|
|
|
// KeyMayExist can lead to a few false positives, but not false negatives.
|
|
|
|
// To make test deterministic, use a much larger number of bits per key-20 than
|
|
|
|
// bits in the key, so that false positives are eliminated
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, KeyMayExist) {
|
2013-07-06 03:49:18 +02:00
|
|
|
do {
|
2013-07-26 21:57:01 +02:00
|
|
|
ReadOptions ropts;
|
|
|
|
std::string value;
|
2014-08-25 23:22:05 +02:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.filter_policy.reset(NewBloomFilterPolicy(20));
|
|
|
|
Options options = CurrentOptions(options_override);
|
2013-10-04 06:49:15 +02:00
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-07-06 03:49:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
2013-07-06 03:49:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "b"));
|
2013-07-26 21:57:01 +02:00
|
|
|
bool value_found = false;
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(
|
|
|
|
db_->KeyMayExist(ropts, handles_[1], "a", &value, &value_found));
|
2013-07-26 21:57:01 +02:00
|
|
|
ASSERT_TRUE(value_found);
|
|
|
|
ASSERT_EQ("b", value);
|
2013-07-06 03:49:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2013-07-26 21:57:01 +02:00
|
|
|
value.clear();
|
2013-08-25 07:48:51 +02:00
|
|
|
|
2014-01-17 21:46:06 +01:00
|
|
|
long numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
long cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(
|
|
|
|
db_->KeyMayExist(ropts, handles_[1], "a", &value, &value_found));
|
2013-08-25 22:40:02 +02:00
|
|
|
ASSERT_TRUE(!value_found);
|
2013-08-25 07:48:51 +02:00
|
|
|
// assert that no new files were opened and no new blocks were
|
|
|
|
// read into block cache.
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
2013-07-06 03:49:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "a"));
|
2013-08-25 07:48:51 +02:00
|
|
|
|
2014-01-17 21:46:06 +01:00
|
|
|
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
2013-07-06 03:49:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1],
|
|
|
|
true /* disallow trivial move */);
|
2013-08-25 07:48:51 +02:00
|
|
|
|
2014-01-17 21:46:06 +01:00
|
|
|
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
2013-07-06 03:49:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "c"));
|
2013-08-25 07:48:51 +02:00
|
|
|
|
2014-01-17 21:46:06 +01:00
|
|
|
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "c", &value));
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
2013-07-11 23:05:31 +02:00
|
|
|
|
2013-12-20 18:35:24 +01:00
|
|
|
// KeyMayExist function only checks data in block caches, which is not used
|
|
|
|
// by plain table format.
|
2014-05-21 20:43:35 +02:00
|
|
|
} while (
|
|
|
|
ChangeOptions(kSkipPlainTable | kSkipHashIndex | kSkipFIFOCompaction));
|
2013-07-06 03:49:18 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, NonBlockingIteration) {
|
2013-08-25 07:48:51 +02:00
|
|
|
do {
|
|
|
|
ReadOptions non_blocking_opts, regular_opts;
|
|
|
|
Options options = CurrentOptions();
|
2013-10-04 06:49:15 +02:00
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
2013-08-25 07:48:51 +02:00
|
|
|
non_blocking_opts.read_tier = kBlockCacheTier;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-08-25 07:48:51 +02:00
|
|
|
// write one kv to the database.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "b"));
|
2013-08-25 07:48:51 +02:00
|
|
|
|
|
|
|
// scan using non-blocking iterator. We should find it because
|
|
|
|
// it is in memtable.
|
2014-02-07 23:47:16 +01:00
|
|
|
Iterator* iter = db_->NewIterator(non_blocking_opts, handles_[1]);
|
2013-08-25 07:48:51 +02:00
|
|
|
int count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
2013-10-04 19:21:03 +02:00
|
|
|
ASSERT_OK(iter->status());
|
2013-08-25 07:48:51 +02:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 1);
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// flush memtable to storage. Now, the key should not be in the
|
|
|
|
// memtable neither in the block cache.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2013-08-25 07:48:51 +02:00
|
|
|
|
|
|
|
// verify that a non-blocking iterator does not find any
|
|
|
|
// kvs. Neither does it do any IOs to storage.
|
2014-01-17 21:46:06 +01:00
|
|
|
long numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
long cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
2014-02-07 23:47:16 +01:00
|
|
|
iter = db_->NewIterator(non_blocking_opts, handles_[1]);
|
2013-08-25 07:48:51 +02:00
|
|
|
count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 0);
|
|
|
|
ASSERT_TRUE(iter->status().IsIncomplete());
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
2013-08-25 07:48:51 +02:00
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// read in the specified block via a regular get
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Get(1, "a"), "b");
|
2013-08-25 07:48:51 +02:00
|
|
|
|
|
|
|
// verify that we can find it via a non-blocking scan
|
2014-01-17 21:46:06 +01:00
|
|
|
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
2014-02-07 23:47:16 +01:00
|
|
|
iter = db_->NewIterator(non_blocking_opts, handles_[1]);
|
2013-08-25 07:48:51 +02:00
|
|
|
count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
2013-10-04 19:21:03 +02:00
|
|
|
ASSERT_OK(iter->status());
|
2013-08-25 07:48:51 +02:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 1);
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
2013-08-25 07:48:51 +02:00
|
|
|
delete iter;
|
|
|
|
|
2013-12-20 18:35:24 +01:00
|
|
|
// This test verifies block cache behaviors, which is not used by plain
|
|
|
|
// table format.
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
// Exclude kHashCuckoo as it does not support iteration currently
|
2014-09-17 21:31:53 +02:00
|
|
|
} while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipHashCuckoo |
|
|
|
|
kSkipMmapReads));
|
2013-08-25 07:48:51 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedNonBlockingIteration) {
|
2015-02-18 20:49:31 +01:00
|
|
|
do {
|
|
|
|
ReadOptions non_blocking_opts, regular_opts;
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
non_blocking_opts.read_tier = kBlockCacheTier;
|
|
|
|
non_blocking_opts.managed = true;
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
// write one kv to the database.
|
|
|
|
ASSERT_OK(Put(1, "a", "b"));
|
|
|
|
|
|
|
|
// scan using non-blocking iterator. We should find it because
|
|
|
|
// it is in memtable.
|
|
|
|
Iterator* iter = db_->NewIterator(non_blocking_opts, handles_[1]);
|
|
|
|
int count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 1);
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// flush memtable to storage. Now, the key should not be in the
|
|
|
|
// memtable neither in the block cache.
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
|
|
|
|
// verify that a non-blocking iterator does not find any
|
|
|
|
// kvs. Neither does it do any IOs to storage.
|
|
|
|
int64_t numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
int64_t cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
|
|
|
iter = db_->NewIterator(non_blocking_opts, handles_[1]);
|
|
|
|
count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 0);
|
|
|
|
ASSERT_TRUE(iter->status().IsIncomplete());
|
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// read in the specified block via a regular get
|
|
|
|
ASSERT_EQ(Get(1, "a"), "b");
|
|
|
|
|
|
|
|
// verify that we can find it via a non-blocking scan
|
|
|
|
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
|
|
|
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
|
|
|
iter = db_->NewIterator(non_blocking_opts, handles_[1]);
|
|
|
|
count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 1);
|
|
|
|
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
|
|
|
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// This test verifies block cache behaviors, which is not used by plain
|
|
|
|
// table format.
|
|
|
|
// Exclude kHashCuckoo as it does not support iteration currently
|
|
|
|
} while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipHashCuckoo |
|
|
|
|
kSkipMmapReads));
|
|
|
|
}
|
|
|
|
|
2013-07-13 01:56:52 +02:00
|
|
|
// A delete is skipped for key if KeyMayExist(key) returns False
|
|
|
|
// Tests Writebatch consistency and proper delete behaviour
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FilterDeletes) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-08-25 23:22:05 +02:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.filter_policy.reset(NewBloomFilterPolicy(20));
|
|
|
|
Options options = CurrentOptions(options_override);
|
2013-08-08 00:20:41 +02:00
|
|
|
options.filter_deletes = true;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-08-08 00:20:41 +02:00
|
|
|
WriteBatch batch;
|
|
|
|
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Delete(handles_[1], "a");
|
2013-08-08 00:20:41 +02:00
|
|
|
dbfull()->Write(WriteOptions(), &batch);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("a", 1), "[ ]"); // Delete skipped
|
2013-08-08 00:20:41 +02:00
|
|
|
batch.Clear();
|
|
|
|
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Put(handles_[1], "a", "b");
|
|
|
|
batch.Delete(handles_[1], "a");
|
2013-08-08 00:20:41 +02:00
|
|
|
dbfull()->Write(WriteOptions(), &batch);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Get(1, "a"), "NOT_FOUND");
|
|
|
|
ASSERT_EQ(AllEntriesFor("a", 1), "[ DEL, b ]"); // Delete issued
|
2013-08-08 00:20:41 +02:00
|
|
|
batch.Clear();
|
|
|
|
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Delete(handles_[1], "c");
|
|
|
|
batch.Put(handles_[1], "c", "d");
|
2013-08-08 00:20:41 +02:00
|
|
|
dbfull()->Write(WriteOptions(), &batch);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Get(1, "c"), "d");
|
|
|
|
ASSERT_EQ(AllEntriesFor("c", 1), "[ d ]"); // Delete skipped
|
2013-08-08 00:20:41 +02:00
|
|
|
batch.Clear();
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1)); // A stray Flush
|
2013-08-08 00:20:41 +02:00
|
|
|
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Delete(handles_[1], "c");
|
2013-08-08 00:20:41 +02:00
|
|
|
dbfull()->Write(WriteOptions(), &batch);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("c", 1), "[ DEL, d ]"); // Delete issued
|
2013-08-08 00:20:41 +02:00
|
|
|
batch.Clear();
|
|
|
|
} while (ChangeCompactOptions());
|
2013-07-13 01:56:52 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetFilterByPrefixBloom) {
|
2015-02-03 02:42:57 +01:00
|
|
|
Options options = last_options_;
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
BlockBasedTableOptions bbto;
|
|
|
|
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
|
|
|
bbto.whole_key_filtering = false;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
WriteOptions wo;
|
|
|
|
ReadOptions ro;
|
|
|
|
FlushOptions fo;
|
|
|
|
fo.wait = true;
|
|
|
|
std::string value;
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "barbarbar", "foo"));
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "barbarbar2", "foo2"));
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "foofoofoo", "bar"));
|
|
|
|
|
|
|
|
dbfull()->Flush(fo);
|
|
|
|
|
|
|
|
ASSERT_EQ("foo", Get("barbarbar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
|
|
|
ASSERT_EQ("foo2", Get("barbarbar2"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("barbarbar3"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
|
|
|
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("barfoofoo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
|
|
|
|
}
|
2014-03-05 18:13:07 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, WholeKeyFilterProp) {
|
2015-02-05 02:03:57 +01:00
|
|
|
Options options = last_options_;
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
|
|
|
|
BlockBasedTableOptions bbto;
|
|
|
|
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
|
|
|
bbto.whole_key_filtering = false;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
WriteOptions wo;
|
|
|
|
ReadOptions ro;
|
|
|
|
FlushOptions fo;
|
|
|
|
fo.wait = true;
|
|
|
|
std::string value;
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "foobar", "foo"));
|
|
|
|
// Needs insert some keys to make sure files are not filtered out by key
|
|
|
|
// ranges.
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
|
|
|
dbfull()->Flush(fo);
|
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("foo", Get("foobar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
|
|
|
|
// Reopen with whole key filtering enabled and prefix extractor
|
|
|
|
// NULL. Bloom filter should be off for both of whole key and
|
|
|
|
// prefix bloom.
|
|
|
|
bbto.whole_key_filtering = true;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
options.prefix_extractor.reset();
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("foo", Get("foobar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
// Write DB with only full key filtering.
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "foobar", "foo"));
|
|
|
|
// Needs insert some keys to make sure files are not filtered out by key
|
|
|
|
// ranges.
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2015-02-05 02:03:57 +01:00
|
|
|
|
|
|
|
// Reopen with both of whole key off and prefix extractor enabled.
|
|
|
|
// Still no bloom filter should be used.
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
|
|
|
bbto.whole_key_filtering = false;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("foo", Get("foobar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
|
|
|
|
// Try to create a DB with mixed files:
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "foobar", "foo"));
|
|
|
|
// Needs insert some keys to make sure files are not filtered out by key
|
|
|
|
// ranges.
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2015-02-05 02:03:57 +01:00
|
|
|
|
|
|
|
options.prefix_extractor.reset();
|
|
|
|
bbto.whole_key_filtering = true;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
// Try to create a DB with mixed files.
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "barfoo", "bar"));
|
|
|
|
// In this case needs insert some keys to make sure files are
|
|
|
|
// not filtered out by key ranges.
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
|
|
|
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
|
|
|
Flush();
|
|
|
|
|
|
|
|
// Now we have two files:
|
|
|
|
// File 1: An older file with prefix bloom.
|
|
|
|
// File 2: A newer file with whole bloom filter.
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
|
|
|
ASSERT_EQ("foo", Get("foobar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4);
|
|
|
|
ASSERT_EQ("bar", Get("barfoo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4);
|
|
|
|
|
|
|
|
// Reopen with the same setting: only whole key is used
|
|
|
|
Reopen(options);
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 5);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 6);
|
|
|
|
ASSERT_EQ("foo", Get("foobar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7);
|
|
|
|
ASSERT_EQ("bar", Get("barfoo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7);
|
|
|
|
|
|
|
|
// Restart with both filters are allowed
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
|
|
|
bbto.whole_key_filtering = true;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
Reopen(options);
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7);
|
|
|
|
// File 1 will has it filtered out.
|
|
|
|
// File 2 will not, as prefix `foo` exists in the file.
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 8);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 10);
|
|
|
|
ASSERT_EQ("foo", Get("foobar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
|
|
|
ASSERT_EQ("bar", Get("barfoo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
|
|
|
|
|
|
|
// Restart with only prefix bloom is allowed.
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
|
|
|
bbto.whole_key_filtering = false;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
Reopen(options);
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12);
|
|
|
|
ASSERT_EQ("foo", Get("foobar"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12);
|
|
|
|
ASSERT_EQ("bar", Get("barfoo"));
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterSeekBeforePrev) {
|
2014-03-05 18:13:07 +01:00
|
|
|
ASSERT_OK(Put("a", "b"));
|
|
|
|
ASSERT_OK(Put("c", "d"));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
ASSERT_OK(Put("0", "f"));
|
|
|
|
ASSERT_OK(Put("1", "h"));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
ASSERT_OK(Put("2", "j"));
|
|
|
|
auto iter = db_->NewIterator(ReadOptions());
|
|
|
|
iter->Seek(Slice("c"));
|
|
|
|
iter->Prev();
|
|
|
|
iter->Seek(Slice("a"));
|
|
|
|
iter->Prev();
|
|
|
|
delete iter;
|
|
|
|
}
|
|
|
|
|
2014-04-10 06:17:14 +02:00
|
|
|
namespace {
|
2014-04-01 23:45:30 +02:00
|
|
|
std::string MakeLongKey(size_t length, char c) {
|
|
|
|
return std::string(length, c);
|
|
|
|
}
|
2014-04-10 06:17:14 +02:00
|
|
|
} // namespace
|
2014-04-01 23:45:30 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterLongKeys) {
|
2014-04-01 23:45:30 +02:00
|
|
|
ASSERT_OK(Put(MakeLongKey(20, 0), "0"));
|
|
|
|
ASSERT_OK(Put(MakeLongKey(32, 2), "2"));
|
|
|
|
ASSERT_OK(Put("a", "b"));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
ASSERT_OK(Put(MakeLongKey(50, 1), "1"));
|
|
|
|
ASSERT_OK(Put(MakeLongKey(127, 3), "3"));
|
|
|
|
ASSERT_OK(Put(MakeLongKey(64, 4), "4"));
|
|
|
|
auto iter = db_->NewIterator(ReadOptions());
|
|
|
|
|
|
|
|
// Create a key that needs to be skipped for Seq too new
|
|
|
|
iter->Seek(MakeLongKey(20, 0));
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(20, 0) + "->0");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(50, 1) + "->1");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(32, 2) + "->2");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(127, 3) + "->3");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(64, 4) + "->4");
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
iter = db_->NewIterator(ReadOptions());
|
|
|
|
iter->Seek(MakeLongKey(50, 1));
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(50, 1) + "->1");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(32, 2) + "->2");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), MakeLongKey(127, 3) + "->3");
|
|
|
|
delete iter;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterNextWithNewerSeq) {
|
2014-03-15 00:06:38 +01:00
|
|
|
ASSERT_OK(Put("0", "0"));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
ASSERT_OK(Put("a", "b"));
|
|
|
|
ASSERT_OK(Put("c", "d"));
|
|
|
|
ASSERT_OK(Put("d", "e"));
|
|
|
|
auto iter = db_->NewIterator(ReadOptions());
|
|
|
|
|
|
|
|
// Create a key that needs to be skipped for Seq too new
|
|
|
|
for (uint64_t i = 0; i < last_options_.max_sequential_skip_in_iterations + 1;
|
|
|
|
i++) {
|
|
|
|
ASSERT_OK(Put("b", "f"));
|
|
|
|
}
|
|
|
|
|
|
|
|
iter->Seek(Slice("a"));
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->b");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->d");
|
|
|
|
delete iter;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterPrevWithNewerSeq) {
|
2014-03-15 00:06:38 +01:00
|
|
|
ASSERT_OK(Put("0", "0"));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
ASSERT_OK(Put("a", "b"));
|
|
|
|
ASSERT_OK(Put("c", "d"));
|
|
|
|
ASSERT_OK(Put("d", "e"));
|
|
|
|
auto iter = db_->NewIterator(ReadOptions());
|
|
|
|
|
|
|
|
// Create a key that needs to be skipped for Seq too new
|
|
|
|
for (uint64_t i = 0; i < last_options_.max_sequential_skip_in_iterations + 1;
|
|
|
|
i++) {
|
|
|
|
ASSERT_OK(Put("b", "f"));
|
|
|
|
}
|
|
|
|
|
|
|
|
iter->Seek(Slice("d"));
|
|
|
|
ASSERT_EQ(IterStatus(iter), "d->e");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->d");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->b");
|
|
|
|
|
|
|
|
iter->Prev();
|
|
|
|
delete iter;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterPrevWithNewerSeq2) {
|
2014-03-15 00:06:38 +01:00
|
|
|
ASSERT_OK(Put("0", "0"));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
ASSERT_OK(Put("a", "b"));
|
|
|
|
ASSERT_OK(Put("c", "d"));
|
|
|
|
ASSERT_OK(Put("d", "e"));
|
|
|
|
auto iter = db_->NewIterator(ReadOptions());
|
|
|
|
iter->Seek(Slice("c"));
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->d");
|
|
|
|
|
|
|
|
// Create a key that needs to be skipped for Seq too new
|
|
|
|
for (uint64_t i = 0; i < last_options_.max_sequential_skip_in_iterations + 1;
|
|
|
|
i++) {
|
|
|
|
ASSERT_OK(Put("b", "f"));
|
|
|
|
}
|
|
|
|
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->b");
|
|
|
|
|
|
|
|
iter->Prev();
|
|
|
|
delete iter;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterEmpty) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->Seek("foo");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
delete iter;
|
|
|
|
} while (ChangeCompactOptions());
|
2011-03-25 21:27:43 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterSingle) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "va"));
|
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
iter->Seek("");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->Seek("a");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
iter->Seek("b");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
delete iter;
|
|
|
|
} while (ChangeCompactOptions());
|
2011-03-25 21:27:43 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterMulti) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "va"));
|
|
|
|
ASSERT_OK(Put(1, "b", "vb"));
|
|
|
|
ASSERT_OK(Put(1, "c", "vc"));
|
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->vc");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->vc");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->vc");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
iter->Seek("");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Seek("a");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Seek("ax");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
2013-11-18 20:32:54 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->Seek("b");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
|
|
|
iter->Seek("z");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
// Switch from reverse to forward
|
|
|
|
iter->SeekToLast();
|
|
|
|
iter->Prev();
|
|
|
|
iter->Prev();
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
|
|
|
|
|
|
|
// Switch from forward to reverse
|
|
|
|
iter->SeekToFirst();
|
|
|
|
iter->Next();
|
|
|
|
iter->Next();
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
|
|
|
|
|
|
|
// Make sure iter stays at snapshot
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "va2"));
|
|
|
|
ASSERT_OK(Put(1, "a2", "va3"));
|
|
|
|
ASSERT_OK(Put(1, "b", "vb2"));
|
|
|
|
ASSERT_OK(Put(1, "c", "vc2"));
|
|
|
|
ASSERT_OK(Delete(1, "b"));
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->vc");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->vc");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->vb");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
delete iter;
|
|
|
|
} while (ChangeCompactOptions());
|
2011-03-25 21:27:43 +01:00
|
|
|
}
|
|
|
|
|
2013-07-28 20:53:08 +02:00
|
|
|
// Check that we can skip over a run of user keys
|
|
|
|
// by using reseek rather than sequential scan
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterReseek) {
|
2015-02-02 23:49:22 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
|
|
|
Options options = CurrentOptions(options_override);
|
2013-07-28 20:53:08 +02:00
|
|
|
options.max_sequential_skip_in_iterations = 3;
|
|
|
|
options.create_if_missing = true;
|
2013-10-04 06:49:15 +02:00
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-07-28 20:53:08 +02:00
|
|
|
|
|
|
|
// insert two keys with same userkey and verify that
|
|
|
|
// reseek is not invoked. For each of these test cases,
|
|
|
|
// verify that we can find the next key "b".
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "one"));
|
|
|
|
ASSERT_OK(Put(1, "a", "two"));
|
|
|
|
ASSERT_OK(Put(1, "b", "bone"));
|
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->SeekToFirst();
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0);
|
2013-07-28 20:53:08 +02:00
|
|
|
ASSERT_EQ(IterStatus(iter), "a->two");
|
|
|
|
iter->Next();
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0);
|
2013-07-28 20:53:08 +02:00
|
|
|
ASSERT_EQ(IterStatus(iter), "b->bone");
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// insert a total of three keys with same userkey and verify
|
|
|
|
// that reseek is still not invoked.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "three"));
|
|
|
|
iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->three");
|
|
|
|
iter->Next();
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0);
|
2013-07-28 20:53:08 +02:00
|
|
|
ASSERT_EQ(IterStatus(iter), "b->bone");
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// insert a total of four keys with same userkey and verify
|
|
|
|
// that reseek is invoked.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "four"));
|
|
|
|
iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->four");
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->Next();
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1);
|
2013-07-28 20:53:08 +02:00
|
|
|
ASSERT_EQ(IterStatus(iter), "b->bone");
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// Testing reverse iterator
|
|
|
|
// At this point, we have three versions of "a" and one version of "b".
|
|
|
|
// The reseek statistics is already at 1.
|
2014-01-17 21:46:06 +01:00
|
|
|
int num_reseeks =
|
|
|
|
(int)TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION);
|
2013-07-28 20:53:08 +02:00
|
|
|
|
|
|
|
// Insert another version of b and assert that reseek is not invoked
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "b", "btwo"));
|
|
|
|
iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->btwo");
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION),
|
|
|
|
num_reseeks);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->Prev();
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION),
|
|
|
|
num_reseeks + 1);
|
2013-07-28 20:53:08 +02:00
|
|
|
ASSERT_EQ(IterStatus(iter), "a->four");
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
// insert two more versions of b. This makes a total of 4 versions
|
|
|
|
// of b and 4 versions of a.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "b", "bthree"));
|
|
|
|
ASSERT_OK(Put(1, "b", "bfour"));
|
|
|
|
iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->bfour");
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION),
|
|
|
|
num_reseeks + 2);
|
2013-07-28 20:53:08 +02:00
|
|
|
iter->Prev();
|
|
|
|
|
|
|
|
// the previous Prev call should have invoked reseek
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION),
|
|
|
|
num_reseeks + 3);
|
2013-07-28 20:53:08 +02:00
|
|
|
ASSERT_EQ(IterStatus(iter), "a->four");
|
|
|
|
delete iter;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterSmallAndLargeMix) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "va"));
|
|
|
|
ASSERT_OK(Put(1, "b", std::string(100000, 'b')));
|
|
|
|
ASSERT_OK(Put(1, "c", "vc"));
|
|
|
|
ASSERT_OK(Put(1, "d", std::string(100000, 'd')));
|
|
|
|
ASSERT_OK(Put(1, "e", std::string(100000, 'e')));
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->" + std::string(100000, 'b'));
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->vc");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "d->" + std::string(100000, 'd'));
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "e->" + std::string(100000, 'e'));
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToLast();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "e->" + std::string(100000, 'e'));
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "d->" + std::string(100000, 'd'));
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "c->vc");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "b->" + std::string(100000, 'b'));
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "a->va");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "(invalid)");
|
|
|
|
|
|
|
|
delete iter;
|
|
|
|
} while (ChangeCompactOptions());
|
2011-03-25 21:27:43 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterMultiWithDelete) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-04-25 21:21:34 +02:00
|
|
|
ASSERT_OK(Put(1, "ka", "va"));
|
|
|
|
ASSERT_OK(Put(1, "kb", "vb"));
|
|
|
|
ASSERT_OK(Put(1, "kc", "vc"));
|
|
|
|
ASSERT_OK(Delete(1, "kb"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, "kb"));
|
2014-02-07 23:47:16 +01:00
|
|
|
|
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2014-04-25 21:21:34 +02:00
|
|
|
iter->Seek("kc");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "kc->vc");
|
2013-03-21 23:59:47 +01:00
|
|
|
if (!CurrentOptions().merge_operator) {
|
|
|
|
// TODO: merge operator does not support backward iteration yet
|
2014-04-25 21:21:34 +02:00
|
|
|
if (kPlainTableAllBytesPrefix != option_config_&&
|
|
|
|
kBlockBasedTableWithWholeKeyHashIndex != option_config_ &&
|
|
|
|
kHashLinkList != option_config_) {
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "ka->va");
|
|
|
|
}
|
2013-03-21 23:59:47 +01:00
|
|
|
}
|
2012-04-17 17:36:46 +02:00
|
|
|
delete iter;
|
|
|
|
} while (ChangeOptions());
|
2011-08-16 03:21:01 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterPrevMaxSkip) {
|
2013-10-18 03:33:18 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2013-10-18 03:33:18 +02:00
|
|
|
for (int i = 0; i < 2; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key1", "v1"));
|
|
|
|
ASSERT_OK(Put(1, "key2", "v2"));
|
|
|
|
ASSERT_OK(Put(1, "key3", "v3"));
|
|
|
|
ASSERT_OK(Put(1, "key4", "v4"));
|
|
|
|
ASSERT_OK(Put(1, "key5", "v5"));
|
2013-10-18 03:33:18 +02:00
|
|
|
}
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
VerifyIterLast("key5->v5", 1);
|
2013-10-18 03:33:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "key5"));
|
|
|
|
VerifyIterLast("key4->v4", 1);
|
2013-10-18 03:33:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "key4"));
|
|
|
|
VerifyIterLast("key3->v3", 1);
|
2013-10-18 03:33:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "key3"));
|
|
|
|
VerifyIterLast("key2->v2", 1);
|
2013-10-18 03:33:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "key2"));
|
|
|
|
VerifyIterLast("key1->v1", 1);
|
2013-10-18 03:33:18 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "key1"));
|
|
|
|
VerifyIterLast("(invalid)", 1);
|
2014-04-25 21:21:34 +02:00
|
|
|
} while (ChangeOptions(kSkipMergePut | kSkipNoSeekToLast));
|
2013-10-18 03:33:18 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IterWithSnapshot) {
|
2015-02-03 21:19:56 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2013-09-28 20:39:08 +02:00
|
|
|
do {
|
2015-02-03 21:19:56 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key1", "val1"));
|
|
|
|
ASSERT_OK(Put(1, "key2", "val2"));
|
|
|
|
ASSERT_OK(Put(1, "key3", "val3"));
|
|
|
|
ASSERT_OK(Put(1, "key4", "val4"));
|
|
|
|
ASSERT_OK(Put(1, "key5", "val5"));
|
2013-09-28 20:39:08 +02:00
|
|
|
|
|
|
|
const Snapshot *snapshot = db_->GetSnapshot();
|
|
|
|
ReadOptions options;
|
|
|
|
options.snapshot = snapshot;
|
2014-02-07 23:47:16 +01:00
|
|
|
Iterator* iter = db_->NewIterator(options, handles_[1]);
|
2013-09-28 20:39:08 +02:00
|
|
|
|
|
|
|
// Put more values after the snapshot
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key100", "val100"));
|
|
|
|
ASSERT_OK(Put(1, "key101", "val101"));
|
2013-09-28 20:39:08 +02:00
|
|
|
|
|
|
|
iter->Seek("key5");
|
|
|
|
ASSERT_EQ(IterStatus(iter), "key5->val5");
|
|
|
|
if (!CurrentOptions().merge_operator) {
|
|
|
|
// TODO: merge operator does not support backward iteration yet
|
2014-04-25 21:21:34 +02:00
|
|
|
if (kPlainTableAllBytesPrefix != option_config_&&
|
|
|
|
kBlockBasedTableWithWholeKeyHashIndex != option_config_ &&
|
|
|
|
kHashLinkList != option_config_) {
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "key4->val4");
|
|
|
|
iter->Prev();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "key3->val3");
|
2013-09-28 20:39:08 +02:00
|
|
|
|
2014-04-25 21:21:34 +02:00
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "key4->val4");
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_EQ(IterStatus(iter), "key5->val5");
|
|
|
|
}
|
2013-09-28 20:39:08 +02:00
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
}
|
|
|
|
db_->ReleaseSnapshot(snapshot);
|
|
|
|
delete iter;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
// skip as HashCuckooRep does not support snapshot
|
|
|
|
} while (ChangeOptions(kSkipHashCuckoo));
|
2013-09-28 20:39:08 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, Recover) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_OK(Put(1, "baz", "v5"));
|
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("v5", Get(1, "baz"));
|
|
|
|
ASSERT_OK(Put(1, "bar", "v2"));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v3"));
|
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v3", Get(1, "foo"));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v4"));
|
|
|
|
ASSERT_EQ("v4", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("v2", Get(1, "bar"));
|
|
|
|
ASSERT_EQ("v5", Get(1, "baz"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RecoverWithTableHandle) {
|
2014-02-12 19:43:27 +01:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2014-02-12 19:43:27 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = 100;
|
|
|
|
options.disable_auto_compactions = true;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-12 19:43:27 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_OK(Put(1, "bar", "v2"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v3"));
|
|
|
|
ASSERT_OK(Put(1, "bar", "v4"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_OK(Put(1, "big", std::string(100, 'a')));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-12 19:43:27 +01:00
|
|
|
|
|
|
|
std::vector<std::vector<FileMetaData>> files;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_GetFilesMetaData(handles_[1], &files);
|
2014-02-12 19:43:27 +01:00
|
|
|
int total_files = 0;
|
|
|
|
for (const auto& level : files) {
|
|
|
|
total_files += level.size();
|
|
|
|
}
|
|
|
|
ASSERT_EQ(total_files, 3);
|
|
|
|
for (const auto& level : files) {
|
|
|
|
for (const auto& file : level) {
|
|
|
|
if (kInfiniteMaxOpenFiles == option_config_) {
|
|
|
|
ASSERT_TRUE(file.table_reader_handle != nullptr);
|
|
|
|
} else {
|
|
|
|
ASSERT_TRUE(file.table_reader_handle == nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (ChangeOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IgnoreRecoveredLog) {
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
std::string backup_logs = dbname_ + "/backup_logs";
|
|
|
|
|
|
|
|
// delete old files in backup_logs directory
|
|
|
|
env_->CreateDirIfMissing(backup_logs);
|
|
|
|
std::vector<std::string> old_files;
|
|
|
|
env_->GetChildren(backup_logs, &old_files);
|
|
|
|
for (auto& file : old_files) {
|
|
|
|
if (file != "." && file != "..") {
|
|
|
|
env_->DeleteFile(backup_logs + "/" + file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
options.wal_dir = dbname_ + "/logs";
|
|
|
|
DestroyAndReopen(options);
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
|
|
|
|
// fill up the DB
|
|
|
|
std::string one, two;
|
|
|
|
PutFixed64(&one, 1);
|
|
|
|
PutFixed64(&two, 2);
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one)));
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one)));
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), Slice("bar"), Slice(one)));
|
|
|
|
|
|
|
|
// copy the logs to backup
|
|
|
|
std::vector<std::string> logs;
|
|
|
|
env_->GetChildren(options.wal_dir, &logs);
|
|
|
|
for (auto& log : logs) {
|
|
|
|
if (log != ".." && log != ".") {
|
|
|
|
CopyFile(options.wal_dir + "/" + log, backup_logs + "/" + log);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// recover the DB
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
ASSERT_EQ(two, Get("foo"));
|
|
|
|
ASSERT_EQ(one, Get("bar"));
|
|
|
|
Close();
|
|
|
|
|
|
|
|
// copy the logs from backup back to wal dir
|
|
|
|
for (auto& log : logs) {
|
|
|
|
if (log != ".." && log != ".") {
|
|
|
|
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// this should ignore the log files, recovery should not happen again
|
|
|
|
// if the recovery happens, the same merge operator would be called twice,
|
|
|
|
// leading to incorrect results
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
ASSERT_EQ(two, Get("foo"));
|
|
|
|
ASSERT_EQ(one, Get("bar"));
|
|
|
|
Close();
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-04-15 18:06:13 +02:00
|
|
|
Close();
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
|
|
|
|
// copy the logs from backup back to wal dir
|
|
|
|
env_->CreateDirIfMissing(options.wal_dir);
|
|
|
|
for (auto& log : logs) {
|
|
|
|
if (log != ".." && log != ".") {
|
|
|
|
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// assert that we successfully recovered only from logs, even though we
|
|
|
|
// destroyed the DB
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
ASSERT_EQ(two, Get("foo"));
|
|
|
|
ASSERT_EQ(one, Get("bar"));
|
2014-04-15 18:06:13 +02:00
|
|
|
|
|
|
|
// Recovery will fail if DB directory doesn't exist.
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
2014-04-15 18:06:13 +02:00
|
|
|
// copy the logs from backup back to wal dir
|
|
|
|
env_->CreateDirIfMissing(options.wal_dir);
|
|
|
|
for (auto& log : logs) {
|
|
|
|
if (log != ".." && log != ".") {
|
|
|
|
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
|
|
|
// we won't be needing this file no more
|
|
|
|
env_->DeleteFile(backup_logs + "/" + log);
|
|
|
|
}
|
|
|
|
}
|
2014-10-29 20:00:01 +01:00
|
|
|
Status s = TryReopen(options);
|
2014-04-15 18:06:13 +02:00
|
|
|
ASSERT_TRUE(!s.ok());
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
} while (ChangeOptions(kSkipHashCuckoo));
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RollLog) {
|
2012-08-18 01:06:05 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_OK(Put(1, "baz", "v5"));
|
2012-08-18 01:06:05 +02:00
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2012-08-18 01:06:05 +02:00
|
|
|
for (int i = 0; i < 10; i++) {
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2012-08-18 01:06:05 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v4"));
|
2012-08-18 01:06:05 +02:00
|
|
|
for (int i = 0; i < 10; i++) {
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2012-08-18 01:06:05 +02:00
|
|
|
}
|
|
|
|
} while (ChangeOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, WAL) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2013-08-08 00:20:41 +02:00
|
|
|
WriteOptions writeOpt = WriteOptions();
|
|
|
|
writeOpt.disableWAL = true;
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1"));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1"));
|
2012-07-05 22:39:28 +02:00
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("v1", Get(1, "bar"));
|
2012-07-05 22:39:28 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
writeOpt.disableWAL = false;
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v2"));
|
2013-08-08 00:20:41 +02:00
|
|
|
writeOpt.disableWAL = true;
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v2"));
|
2012-07-05 22:39:28 +02:00
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2013-08-08 00:20:41 +02:00
|
|
|
// Both value's should be present.
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_EQ("v2", Get(1, "bar"));
|
|
|
|
ASSERT_EQ("v2", Get(1, "foo"));
|
2012-07-05 22:39:28 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
writeOpt.disableWAL = true;
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v3"));
|
2013-08-08 00:20:41 +02:00
|
|
|
writeOpt.disableWAL = false;
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v3"));
|
2012-07-05 22:39:28 +02:00
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2013-08-08 00:20:41 +02:00
|
|
|
// again both values should be present.
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_EQ("v3", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("v3", Get(1, "bar"));
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2012-07-05 22:39:28 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CheckLock) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
DB* localdb;
|
|
|
|
Options options = CurrentOptions();
|
2014-10-29 20:00:01 +01:00
|
|
|
ASSERT_OK(TryReopen(options));
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// second open should fail
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(!(DB::Open(options, dbname_, &localdb)).ok());
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2012-08-18 09:26:50 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FlushMultipleMemtable) {
|
2013-09-05 02:24:35 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
WriteOptions writeOpt = WriteOptions();
|
|
|
|
writeOpt.disableWAL = true;
|
|
|
|
options.max_write_buffer_number = 4;
|
|
|
|
options.min_write_buffer_number_to_merge = 3;
|
Support saving history in memtable_list
Summary:
For transactions, we are using the memtables to validate that there are no write conflicts. But after flushing, we don't have any memtables, and transactions could fail to commit. So we want to someone keep around some extra history to use for conflict checking. In addition, we want to provide a way to increase the size of this history if too many transactions fail to commit.
After chatting with people, it seems like everyone prefers just using Memtables to store this history (instead of a separate history structure). It seems like the best place for this is abstracted inside the memtable_list. I decide to create a separate list in MemtableListVersion as using the same list complicated the flush/installalflushresults logic too much.
This diff adds a new parameter to control how much memtable history to keep around after flushing. However, it sounds like people aren't too fond of adding new parameters. So I am making the default size of flushed+not-flushed memtables be set to max_write_buffers. This should not change the maximum amount of memory used, but make it more likely we're using closer the the limit. (We are now postponing deleting flushed memtables until the max_write_buffer limit is reached). So while we might use more memory on average, we are still obeying the limit set (and you could argue it's better to go ahead and use up memory now instead of waiting for a write stall to happen to test this limit).
However, if people are opposed to this default behavior, we can easily set it to 0 and require this parameter be set in order to use transactions.
Test Plan: Added a xfunc test to play around with setting different values of this parameter in all tests. Added testing in memtablelist_test and planning on adding more testing here.
Reviewers: sdong, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37443
2015-05-29 01:34:24 +02:00
|
|
|
options.max_write_buffer_number_to_maintain = -1;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1"));
|
|
|
|
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("v1", Get(1, "bar"));
|
|
|
|
ASSERT_OK(Flush(1));
|
2013-09-05 02:24:35 +02:00
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, NumImmutableMemTable) {
|
2013-10-05 00:17:54 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
WriteOptions writeOpt = WriteOptions();
|
|
|
|
writeOpt.disableWAL = true;
|
|
|
|
options.max_write_buffer_number = 4;
|
|
|
|
options.min_write_buffer_number_to_merge = 3;
|
Support saving history in memtable_list
Summary:
For transactions, we are using the memtables to validate that there are no write conflicts. But after flushing, we don't have any memtables, and transactions could fail to commit. So we want to someone keep around some extra history to use for conflict checking. In addition, we want to provide a way to increase the size of this history if too many transactions fail to commit.
After chatting with people, it seems like everyone prefers just using Memtables to store this history (instead of a separate history structure). It seems like the best place for this is abstracted inside the memtable_list. I decide to create a separate list in MemtableListVersion as using the same list complicated the flush/installalflushresults logic too much.
This diff adds a new parameter to control how much memtable history to keep around after flushing. However, it sounds like people aren't too fond of adding new parameters. So I am making the default size of flushed+not-flushed memtables be set to max_write_buffers. This should not change the maximum amount of memory used, but make it more likely we're using closer the the limit. (We are now postponing deleting flushed memtables until the max_write_buffer limit is reached). So while we might use more memory on average, we are still obeying the limit set (and you could argue it's better to go ahead and use up memory now instead of waiting for a write stall to happen to test this limit).
However, if people are opposed to this default behavior, we can easily set it to 0 and require this parameter be set in order to use transactions.
Test Plan: Added a xfunc test to play around with setting different values of this parameter in all tests. Added testing in memtablelist_test and planning on adding more testing here.
Reviewers: sdong, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37443
2015-05-29 01:34:24 +02:00
|
|
|
options.max_write_buffer_number_to_maintain = 0;
|
2013-10-05 00:17:54 +02:00
|
|
|
options.write_buffer_size = 1000000;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-10-05 00:17:54 +02:00
|
|
|
|
2014-03-13 00:40:14 +01:00
|
|
|
std::string big_value(1000000 * 2, 'x');
|
2013-10-05 00:17:54 +02:00
|
|
|
std::string num;
|
2013-11-18 20:32:54 +01:00
|
|
|
SetPerfLevel(kEnableTime);;
|
2014-07-10 20:35:07 +02:00
|
|
|
ASSERT_TRUE(GetPerfLevel() == kEnableTime);
|
2013-10-05 00:17:54 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k1", big_value));
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(handles_[1],
|
|
|
|
"rocksdb.num-immutable-mem-table", &num));
|
2013-10-05 00:17:54 +02:00
|
|
|
ASSERT_EQ(num, "0");
|
2014-04-23 02:17:33 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
2013-11-18 20:32:54 +01:00
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
Get(1, "k1");
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_EQ(1, (int) perf_context.get_from_memtable_count);
|
2013-10-05 00:17:54 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k2", big_value));
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(handles_[1],
|
|
|
|
"rocksdb.num-immutable-mem-table", &num));
|
2013-10-05 00:17:54 +02:00
|
|
|
ASSERT_EQ(num, "1");
|
2014-04-23 02:17:33 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-imm-mem-tables", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
|
|
|
|
2013-11-18 20:32:54 +01:00
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
Get(1, "k1");
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_EQ(2, (int) perf_context.get_from_memtable_count);
|
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
Get(1, "k2");
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_EQ(1, (int) perf_context.get_from_memtable_count);
|
2013-10-05 00:17:54 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k3", big_value));
|
2014-03-31 21:44:54 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.cur-size-active-mem-table", &num));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(handles_[1],
|
|
|
|
"rocksdb.num-immutable-mem-table", &num));
|
2013-10-05 00:17:54 +02:00
|
|
|
ASSERT_EQ(num, "2");
|
2014-04-23 02:17:33 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-imm-mem-tables", &num));
|
|
|
|
ASSERT_EQ(num, "2");
|
2013-11-18 20:32:54 +01:00
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
Get(1, "k2");
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_EQ(2, (int) perf_context.get_from_memtable_count);
|
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
Get(1, "k3");
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_EQ(1, (int) perf_context.get_from_memtable_count);
|
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
Get(1, "k1");
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_EQ(3, (int) perf_context.get_from_memtable_count);
|
2013-10-05 00:17:54 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(handles_[1],
|
|
|
|
"rocksdb.num-immutable-mem-table", &num));
|
2013-10-05 00:17:54 +02:00
|
|
|
ASSERT_EQ(num, "0");
|
2014-03-31 21:44:54 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty(
|
|
|
|
handles_[1], "rocksdb.cur-size-active-mem-table", &num));
|
2014-05-09 20:01:54 +02:00
|
|
|
// "200" is the size of the metadata of an empty skiplist, this would
|
2014-03-27 19:59:37 +01:00
|
|
|
// break if we change the default skiplist implementation
|
2014-05-09 20:01:54 +02:00
|
|
|
ASSERT_EQ(num, "200");
|
2015-03-19 00:11:02 +01:00
|
|
|
|
|
|
|
uint64_t int_num;
|
|
|
|
uint64_t base_total_size;
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty(
|
|
|
|
handles_[1], "rocksdb.estimate-num-keys", &base_total_size));
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Delete(writeOpt, handles_[1], "k2"));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k3", ""));
|
|
|
|
ASSERT_OK(dbfull()->Delete(writeOpt, handles_[1], "k3"));
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty(
|
|
|
|
handles_[1], "rocksdb.num-deletes-active-mem-table", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 2U);
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-active-mem-table", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 3U);
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k2", big_value));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k2", big_value));
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty(
|
|
|
|
handles_[1], "rocksdb.num-entries-imm-mem-tables", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 4U);
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty(
|
|
|
|
handles_[1], "rocksdb.num-deletes-imm-mem-tables", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 2U);
|
|
|
|
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty(
|
|
|
|
handles_[1], "rocksdb.estimate-num-keys", &int_num));
|
|
|
|
ASSERT_EQ(int_num, base_total_size + 1);
|
|
|
|
|
2013-11-18 20:32:54 +01:00
|
|
|
SetPerfLevel(kDisable);
|
2014-07-10 20:35:07 +02:00
|
|
|
ASSERT_TRUE(GetPerfLevel() == kDisable);
|
2013-10-05 00:17:54 +02:00
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2014-03-18 20:37:42 +01:00
|
|
|
class SleepingBackgroundTask {
|
|
|
|
public:
|
2014-04-08 23:57:00 +02:00
|
|
|
SleepingBackgroundTask()
|
|
|
|
: bg_cv_(&mutex_), should_sleep_(true), done_with_sleep_(false) {}
|
2014-03-18 20:37:42 +01:00
|
|
|
void DoSleep() {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
while (should_sleep_) {
|
|
|
|
bg_cv_.Wait();
|
|
|
|
}
|
2014-04-08 23:57:00 +02:00
|
|
|
done_with_sleep_ = true;
|
|
|
|
bg_cv_.SignalAll();
|
2014-03-18 20:37:42 +01:00
|
|
|
}
|
|
|
|
void WakeUp() {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
should_sleep_ = false;
|
|
|
|
bg_cv_.SignalAll();
|
|
|
|
}
|
2014-04-08 23:57:00 +02:00
|
|
|
void WaitUntilDone() {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
while (!done_with_sleep_) {
|
|
|
|
bg_cv_.Wait();
|
|
|
|
}
|
|
|
|
}
|
2014-03-18 20:37:42 +01:00
|
|
|
|
|
|
|
static void DoSleepTask(void* arg) {
|
|
|
|
reinterpret_cast<SleepingBackgroundTask*>(arg)->DoSleep();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
port::Mutex mutex_;
|
|
|
|
port::CondVar bg_cv_; // Signalled when background work finishes
|
|
|
|
bool should_sleep_;
|
2014-04-08 23:57:00 +02:00
|
|
|
bool done_with_sleep_;
|
2014-03-18 20:37:42 +01:00
|
|
|
};
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FlushEmptyColumnFamily) {
|
2014-09-05 21:01:01 +02:00
|
|
|
// Block flush thread and disable compaction thread
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
SleepingBackgroundTask sleeping_task_low;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
|
|
|
Env::Priority::LOW);
|
|
|
|
SleepingBackgroundTask sleeping_task_high;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_high,
|
|
|
|
Env::Priority::HIGH);
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
// disable compaction
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
WriteOptions writeOpt = WriteOptions();
|
|
|
|
writeOpt.disableWAL = true;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.min_write_buffer_number_to_merge = 1;
|
Support saving history in memtable_list
Summary:
For transactions, we are using the memtables to validate that there are no write conflicts. But after flushing, we don't have any memtables, and transactions could fail to commit. So we want to someone keep around some extra history to use for conflict checking. In addition, we want to provide a way to increase the size of this history if too many transactions fail to commit.
After chatting with people, it seems like everyone prefers just using Memtables to store this history (instead of a separate history structure). It seems like the best place for this is abstracted inside the memtable_list. I decide to create a separate list in MemtableListVersion as using the same list complicated the flush/installalflushresults logic too much.
This diff adds a new parameter to control how much memtable history to keep around after flushing. However, it sounds like people aren't too fond of adding new parameters. So I am making the default size of flushed+not-flushed memtables be set to max_write_buffers. This should not change the maximum amount of memory used, but make it more likely we're using closer the the limit. (We are now postponing deleting flushed memtables until the max_write_buffer limit is reached). So while we might use more memory on average, we are still obeying the limit set (and you could argue it's better to go ahead and use up memory now instead of waiting for a write stall to happen to test this limit).
However, if people are opposed to this default behavior, we can easily set it to 0 and require this parameter be set in order to use transactions.
Test Plan: Added a xfunc test to play around with setting different values of this parameter in all tests. Added testing in memtablelist_test and planning on adding more testing here.
Reviewers: sdong, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37443
2015-05-29 01:34:24 +02:00
|
|
|
options.max_write_buffer_number_to_maintain = 1;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-09-05 21:01:01 +02:00
|
|
|
|
|
|
|
// Compaction can still go through even if no thread can flush the
|
|
|
|
// mem table.
|
|
|
|
ASSERT_OK(Flush(0));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
|
|
|
|
// Insert can go through
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[0], "foo", "v1"));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1"));
|
|
|
|
|
|
|
|
ASSERT_EQ("v1", Get(0, "foo"));
|
|
|
|
ASSERT_EQ("v1", Get(1, "bar"));
|
|
|
|
|
|
|
|
sleeping_task_high.WakeUp();
|
|
|
|
sleeping_task_high.WaitUntilDone();
|
|
|
|
|
|
|
|
// Flush can still go through.
|
|
|
|
ASSERT_OK(Flush(0));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
|
|
|
|
sleeping_task_low.WakeUp();
|
|
|
|
sleeping_task_low.WaitUntilDone();
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetProperty) {
|
2014-03-18 20:37:42 +01:00
|
|
|
// Set sizes to both background thread pool to be 1 and block them.
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
2014-03-19 23:40:12 +01:00
|
|
|
SleepingBackgroundTask sleeping_task_low;
|
2014-03-18 20:37:42 +01:00
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
|
|
|
Env::Priority::LOW);
|
2014-03-19 23:40:12 +01:00
|
|
|
SleepingBackgroundTask sleeping_task_high;
|
2014-03-18 20:37:42 +01:00
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_high,
|
|
|
|
Env::Priority::HIGH);
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
WriteOptions writeOpt = WriteOptions();
|
|
|
|
writeOpt.disableWAL = true;
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.level0_file_num_compaction_trigger = 1;
|
|
|
|
options.compaction_options_universal.size_ratio = 50;
|
|
|
|
options.max_background_compactions = 1;
|
|
|
|
options.max_background_flushes = 1;
|
|
|
|
options.max_write_buffer_number = 10;
|
|
|
|
options.min_write_buffer_number_to_merge = 1;
|
Support saving history in memtable_list
Summary:
For transactions, we are using the memtables to validate that there are no write conflicts. But after flushing, we don't have any memtables, and transactions could fail to commit. So we want to someone keep around some extra history to use for conflict checking. In addition, we want to provide a way to increase the size of this history if too many transactions fail to commit.
After chatting with people, it seems like everyone prefers just using Memtables to store this history (instead of a separate history structure). It seems like the best place for this is abstracted inside the memtable_list. I decide to create a separate list in MemtableListVersion as using the same list complicated the flush/installalflushresults logic too much.
This diff adds a new parameter to control how much memtable history to keep around after flushing. However, it sounds like people aren't too fond of adding new parameters. So I am making the default size of flushed+not-flushed memtables be set to max_write_buffers. This should not change the maximum amount of memory used, but make it more likely we're using closer the the limit. (We are now postponing deleting flushed memtables until the max_write_buffer limit is reached). So while we might use more memory on average, we are still obeying the limit set (and you could argue it's better to go ahead and use up memory now instead of waiting for a write stall to happen to test this limit).
However, if people are opposed to this default behavior, we can easily set it to 0 and require this parameter be set in order to use transactions.
Test Plan: Added a xfunc test to play around with setting different values of this parameter in all tests. Added testing in memtablelist_test and planning on adding more testing here.
Reviewers: sdong, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37443
2015-05-29 01:34:24 +02:00
|
|
|
options.max_write_buffer_number_to_maintain = 0;
|
2014-03-18 20:37:42 +01:00
|
|
|
options.write_buffer_size = 1000000;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-03-18 20:37:42 +01:00
|
|
|
|
|
|
|
std::string big_value(1000000 * 2, 'x');
|
|
|
|
std::string num;
|
2014-07-29 00:28:53 +02:00
|
|
|
uint64_t int_num;
|
2014-03-18 20:37:42 +01:00
|
|
|
SetPerfLevel(kEnableTime);
|
|
|
|
|
2014-08-05 20:27:34 +02:00
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 0U);
|
|
|
|
|
2014-03-18 20:37:42 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, "k1", big_value));
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num));
|
|
|
|
ASSERT_EQ(num, "0");
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.mem-table-flush-pending", &num));
|
|
|
|
ASSERT_EQ(num, "0");
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num));
|
|
|
|
ASSERT_EQ(num, "0");
|
2014-07-28 23:50:16 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
2014-03-18 20:37:42 +01:00
|
|
|
perf_context.Reset();
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, "k2", big_value));
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
2014-07-28 23:50:16 +02:00
|
|
|
ASSERT_OK(dbfull()->Delete(writeOpt, "k-non-existing"));
|
2014-03-18 20:37:42 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, "k3", big_value));
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num));
|
|
|
|
ASSERT_EQ(num, "2");
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.mem-table-flush-pending", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num));
|
|
|
|
ASSERT_EQ(num, "0");
|
2014-07-28 23:50:16 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
2015-03-19 00:11:02 +01:00
|
|
|
ASSERT_EQ(num, "2");
|
2014-07-29 00:28:53 +02:00
|
|
|
// Verify the same set of properties through GetIntProperty
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.num-immutable-mem-table", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 2U);
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.mem-table-flush-pending", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 1U);
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.compaction-pending", &int_num));
|
2014-07-31 23:48:00 +02:00
|
|
|
ASSERT_EQ(int_num, 0U);
|
2014-07-29 00:28:53 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &int_num));
|
2015-03-19 00:11:02 +01:00
|
|
|
ASSERT_EQ(int_num, 2U);
|
2014-03-18 20:37:42 +01:00
|
|
|
|
2014-08-05 20:27:34 +02:00
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 0U);
|
|
|
|
|
2014-03-18 20:37:42 +01:00
|
|
|
sleeping_task_high.WakeUp();
|
2014-04-08 23:57:00 +02:00
|
|
|
sleeping_task_high.WaitUntilDone();
|
2014-03-18 20:37:42 +01:00
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, "k4", big_value));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, "k5", big_value));
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.mem-table-flush-pending", &num));
|
|
|
|
ASSERT_EQ(num, "0");
|
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num));
|
|
|
|
ASSERT_EQ(num, "1");
|
2014-07-28 23:50:16 +02:00
|
|
|
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
|
|
|
ASSERT_EQ(num, "4");
|
2014-08-05 20:27:34 +02:00
|
|
|
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
|
|
|
ASSERT_GT(int_num, 0U);
|
|
|
|
|
2014-03-18 20:37:42 +01:00
|
|
|
sleeping_task_low.WakeUp();
|
2014-04-08 23:57:00 +02:00
|
|
|
sleeping_task_low.WaitUntilDone();
|
2014-08-05 20:27:34 +02:00
|
|
|
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
options.max_open_files = 10;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-08-05 20:27:34 +02:00
|
|
|
// After reopening, no table reader is loaded, so no memory for table readers
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 0U);
|
|
|
|
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &int_num));
|
|
|
|
ASSERT_GT(int_num, 0U);
|
|
|
|
|
|
|
|
// After reading a key, at least one table reader is loaded.
|
|
|
|
Get("k5");
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
|
|
|
ASSERT_GT(int_num, 0U);
|
2015-02-12 02:10:43 +01:00
|
|
|
|
|
|
|
// Test rocksdb.num-live-versions
|
|
|
|
{
|
|
|
|
options.level0_file_num_compaction_trigger = 20;
|
|
|
|
Reopen(options);
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 1U);
|
|
|
|
|
|
|
|
// Use an iterator to hold current version
|
|
|
|
std::unique_ptr<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, "k6", big_value));
|
|
|
|
Flush();
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 2U);
|
|
|
|
|
|
|
|
// Use an iterator to hold current version
|
|
|
|
std::unique_ptr<Iterator> iter2(dbfull()->NewIterator(ReadOptions()));
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, "k7", big_value));
|
|
|
|
Flush();
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 3U);
|
|
|
|
|
|
|
|
iter2.reset();
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 2U);
|
|
|
|
|
|
|
|
iter1.reset();
|
|
|
|
ASSERT_TRUE(
|
|
|
|
dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num));
|
|
|
|
ASSERT_EQ(int_num, 1U);
|
|
|
|
}
|
2014-03-18 20:37:42 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FLUSH) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2013-08-08 00:20:41 +02:00
|
|
|
WriteOptions writeOpt = WriteOptions();
|
|
|
|
writeOpt.disableWAL = true;
|
2013-11-18 20:32:54 +01:00
|
|
|
SetPerfLevel(kEnableTime);;
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1"));
|
2013-08-08 00:20:41 +02:00
|
|
|
// this will now also flush the last 2 writes
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1"));
|
2012-07-06 20:42:09 +02:00
|
|
|
|
2013-11-18 20:32:54 +01:00
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
Get(1, "foo");
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_TRUE((int) perf_context.get_from_output_files_time > 0);
|
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
2014-03-12 19:50:10 +01:00
|
|
|
ASSERT_EQ("v1", Get(1, "bar"));
|
2012-07-06 20:42:09 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
writeOpt.disableWAL = true;
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v2"));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v2"));
|
|
|
|
ASSERT_OK(Flush(1));
|
2012-07-06 20:42:09 +02:00
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v2", Get(1, "bar"));
|
2013-11-18 20:32:54 +01:00
|
|
|
perf_context.Reset();
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v2", Get(1, "foo"));
|
2013-11-18 20:32:54 +01:00
|
|
|
ASSERT_TRUE((int) perf_context.get_from_output_files_time > 0);
|
2012-07-06 20:42:09 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
writeOpt.disableWAL = false;
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v3"));
|
|
|
|
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v3"));
|
|
|
|
ASSERT_OK(Flush(1));
|
2012-07-06 20:42:09 +02:00
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2013-08-08 00:20:41 +02:00
|
|
|
// 'foo' should be there because its put
|
|
|
|
// has WAL enabled.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v3", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("v3", Get(1, "bar"));
|
2013-11-18 20:32:54 +01:00
|
|
|
|
|
|
|
SetPerfLevel(kDisable);
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2012-07-06 20:42:09 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RecoveryWithEmptyLog) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_OK(Put(1, "foo", "v2"));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v3"));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v3", Get(1, "foo"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2011-06-22 04:36:45 +02:00
|
|
|
// Check that writes done during a memtable compaction are recovered
|
|
|
|
// if the database is shutdown during the memtable compaction.
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RecoverDuringMemtableCompaction) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2012-04-17 17:36:46 +02:00
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 1000000;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-06-22 04:36:45 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Trigger a long memtable compaction and reopen the database during it
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v1")); // Goes to 1st log file
|
|
|
|
ASSERT_OK(Put(1, "big1", std::string(10000000, 'x'))); // Fills memtable
|
|
|
|
ASSERT_OK(Put(1, "big2", std::string(1000, 'y'))); // Triggers compaction
|
|
|
|
ASSERT_OK(Put(1, "bar", "v2")); // Goes to new log file
|
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("v2", Get(1, "bar"));
|
|
|
|
ASSERT_EQ(std::string(10000000, 'x'), Get(1, "big1"));
|
|
|
|
ASSERT_EQ(std::string(1000, 'y'), Get(1, "big2"));
|
2012-04-17 17:36:46 +02:00
|
|
|
} while (ChangeOptions());
|
2011-06-22 04:36:45 +02:00
|
|
|
}
|
|
|
|
|
2015-01-29 00:31:48 +01:00
|
|
|
// false positive TSAN report on shared_ptr --
|
|
|
|
// https://groups.google.com/forum/#!topic/thread-sanitizer/vz_s-t226Vg
|
|
|
|
#ifndef ROCKSDB_TSAN_RUN
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FlushSchedule) {
|
2014-09-11 03:46:09 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.level0_stop_writes_trigger = 1 << 10;
|
|
|
|
options.level0_slowdown_writes_trigger = 1 << 10;
|
|
|
|
options.min_write_buffer_number_to_merge = 1;
|
Support saving history in memtable_list
Summary:
For transactions, we are using the memtables to validate that there are no write conflicts. But after flushing, we don't have any memtables, and transactions could fail to commit. So we want to someone keep around some extra history to use for conflict checking. In addition, we want to provide a way to increase the size of this history if too many transactions fail to commit.
After chatting with people, it seems like everyone prefers just using Memtables to store this history (instead of a separate history structure). It seems like the best place for this is abstracted inside the memtable_list. I decide to create a separate list in MemtableListVersion as using the same list complicated the flush/installalflushresults logic too much.
This diff adds a new parameter to control how much memtable history to keep around after flushing. However, it sounds like people aren't too fond of adding new parameters. So I am making the default size of flushed+not-flushed memtables be set to max_write_buffers. This should not change the maximum amount of memory used, but make it more likely we're using closer the the limit. (We are now postponing deleting flushed memtables until the max_write_buffer limit is reached). So while we might use more memory on average, we are still obeying the limit set (and you could argue it's better to go ahead and use up memory now instead of waiting for a write stall to happen to test this limit).
However, if people are opposed to this default behavior, we can easily set it to 0 and require this parameter be set in order to use transactions.
Test Plan: Added a xfunc test to play around with setting different values of this parameter in all tests. Added testing in memtablelist_test and planning on adding more testing here.
Reviewers: sdong, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37443
2015-05-29 01:34:24 +02:00
|
|
|
options.max_write_buffer_number_to_maintain = 1;
|
2014-09-11 03:46:09 +02:00
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.write_buffer_size = 100 * 1000;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-09-11 03:46:09 +02:00
|
|
|
std::vector<std::thread> threads;
|
|
|
|
|
2014-09-12 00:36:30 +02:00
|
|
|
std::atomic<int> thread_num(0);
|
2014-09-11 03:46:09 +02:00
|
|
|
// each column family will have 5 thread, each thread generating 2 memtables.
|
|
|
|
// each column family should end up with 10 table files
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
|
|
|
threads.emplace_back([&]() {
|
|
|
|
int a = thread_num.fetch_add(1);
|
|
|
|
Random rnd(a);
|
2014-09-12 00:36:30 +02:00
|
|
|
WriteOptions wo;
|
2014-09-11 03:46:09 +02:00
|
|
|
// this should fill up 2 memtables
|
|
|
|
for (int k = 0; k < 5000; ++k) {
|
2014-09-12 00:36:30 +02:00
|
|
|
ASSERT_OK(db_->Put(wo, handles_[a & 1], RandomString(&rnd, 13), ""));
|
2014-09-11 03:46:09 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& t : threads) {
|
|
|
|
t.join();
|
|
|
|
}
|
|
|
|
|
2014-09-11 04:14:17 +02:00
|
|
|
auto default_tables = GetNumberOfSstFilesForColumnFamily(db_, "default");
|
|
|
|
auto pikachu_tables = GetNumberOfSstFilesForColumnFamily(db_, "pikachu");
|
|
|
|
ASSERT_LE(default_tables, static_cast<uint64_t>(10));
|
|
|
|
ASSERT_GT(default_tables, static_cast<uint64_t>(0));
|
|
|
|
ASSERT_LE(pikachu_tables, static_cast<uint64_t>(10));
|
|
|
|
ASSERT_GT(pikachu_tables, static_cast<uint64_t>(0));
|
2014-09-11 03:46:09 +02:00
|
|
|
}
|
2015-01-29 00:31:48 +01:00
|
|
|
#endif // enabled only if not TSAN run
|
2014-09-11 03:46:09 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, MinorCompactionsHappen) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-08-08 00:20:41 +02:00
|
|
|
options.write_buffer_size = 10000;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
const int N = 500;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
int starting_num_tables = TotalTableFiles(1);
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < N; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i) + std::string(1000, 'v')));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
int ending_num_tables = TotalTableFiles(1);
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_GT(ending_num_tables, starting_num_tables);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < N; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Key(i) + std::string(1000, 'v'), Get(1, Key(i)));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < N; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Key(i) + std::string(1000, 'v'), Get(1, Key(i)));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
} while (ChangeCompactOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManifestRollOver) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-08-08 00:20:41 +02:00
|
|
|
options.max_manifest_file_size = 10 ; // 10 bytes
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-08-08 00:20:41 +02:00
|
|
|
{
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "manifest_key1", std::string(1000, '1')));
|
|
|
|
ASSERT_OK(Put(1, "manifest_key2", std::string(1000, '2')));
|
|
|
|
ASSERT_OK(Put(1, "manifest_key3", std::string(1000, '3')));
|
|
|
|
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
|
|
|
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
|
|
|
|
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
2013-10-18 23:50:54 +02:00
|
|
|
ASSERT_GT(manifest_after_flush, manifest_before_flush);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
|
2013-08-08 00:20:41 +02:00
|
|
|
// check if a new manifest file got inserted or not.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(std::string(1000, '1'), Get(1, "manifest_key1"));
|
|
|
|
ASSERT_EQ(std::string(1000, '2'), Get(1, "manifest_key2"));
|
|
|
|
ASSERT_EQ(std::string(1000, '3'), Get(1, "manifest_key3"));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
} while (ChangeCompactOptions());
|
2013-01-11 02:18:50 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IdentityAcrossRestarts) {
|
2013-10-18 23:50:54 +02:00
|
|
|
do {
|
2013-12-03 15:39:07 +01:00
|
|
|
std::string id1;
|
|
|
|
ASSERT_OK(db_->GetDbIdentity(id1));
|
2013-10-18 23:50:54 +02:00
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2013-12-03 15:39:07 +01:00
|
|
|
std::string id2;
|
|
|
|
ASSERT_OK(db_->GetDbIdentity(id2));
|
2013-10-18 23:50:54 +02:00
|
|
|
// id1 should match id2 because identity was not regenerated
|
2013-12-03 15:39:07 +01:00
|
|
|
ASSERT_EQ(id1.compare(id2), 0);
|
2013-10-18 23:50:54 +02:00
|
|
|
|
2013-12-03 15:39:07 +01:00
|
|
|
std::string idfilename = IdentityFileName(dbname_);
|
2013-10-18 23:50:54 +02:00
|
|
|
ASSERT_OK(env_->DeleteFile(idfilename));
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2013-12-03 15:39:07 +01:00
|
|
|
std::string id3;
|
|
|
|
ASSERT_OK(db_->GetDbIdentity(id3));
|
|
|
|
// id1 should NOT match id3 because identity was regenerated
|
|
|
|
ASSERT_NE(id1.compare(id3), 0);
|
2013-10-18 23:50:54 +02:00
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RecoverWithLargeLog) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
{
|
|
|
|
Options options = CurrentOptions();
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "big1", std::string(200000, '1')));
|
|
|
|
ASSERT_OK(Put(1, "big2", std::string(200000, '2')));
|
|
|
|
ASSERT_OK(Put(1, "small3", std::string(10, '3')));
|
|
|
|
ASSERT_OK(Put(1, "small4", std::string(10, '4')));
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure that if we re-open with a small write buffer size that
|
|
|
|
// we flush table files in the middle of a large log file.
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-08-08 00:20:41 +02:00
|
|
|
options.write_buffer_size = 100000;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 3);
|
|
|
|
ASSERT_EQ(std::string(200000, '1'), Get(1, "big1"));
|
|
|
|
ASSERT_EQ(std::string(200000, '2'), Get(1, "big2"));
|
|
|
|
ASSERT_EQ(std::string(10, '3'), Get(1, "small3"));
|
|
|
|
ASSERT_EQ(std::string(10, '4'), Get(1, "small4"));
|
|
|
|
ASSERT_GT(NumTableFilesAtLevel(0, 1), 1);
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionsGenerateMultipleFiles) {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2011-03-18 23:37:00 +01:00
|
|
|
options.write_buffer_size = 100000000; // Large write buffer
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
// Write 8MB (80 values, each 100K)
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
2011-03-18 23:37:00 +01:00
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int i = 0; i < 80; i++) {
|
|
|
|
values.push_back(RandomString(&rnd, 100000));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), values[i]));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reopening moves updates to level-0
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1],
|
|
|
|
true /* disallow trivial move */);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_GT(NumTableFilesAtLevel(1, 1), 1);
|
2011-03-18 23:37:00 +01:00
|
|
|
for (int i = 0; i < 80; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Get(1, Key(i)), values[i]);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
TEST_F(DBTest, TrivialMoveOneFile) {
|
|
|
|
int32_t trivial_move = 0;
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBImpl::BackgroundCompaction:TrivialMove",
|
|
|
|
[&](void* arg) { trivial_move++; });
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
Options options;
|
|
|
|
options.write_buffer_size = 100000000;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
int32_t num_keys = 80;
|
|
|
|
int32_t value_size = 100 * 1024; // 100 KB
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int i = 0; i < num_keys; i++) {
|
|
|
|
values.push_back(RandomString(&rnd, value_size));
|
|
|
|
ASSERT_OK(Put(Key(i), values[i]));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reopening moves updates to L0
|
|
|
|
Reopen(options);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 0), 1); // 1 file in L0
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // 0 files in L1
|
|
|
|
|
|
|
|
std::vector<LiveFileMetaData> metadata;
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
|
|
|
ASSERT_EQ(metadata.size(), 1U);
|
|
|
|
LiveFileMetaData level0_file = metadata[0]; // L0 file meta
|
|
|
|
|
|
|
|
// Compaction will initiate a trivial move from L0 to L1
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
|
|
|
|
// File moved From L0 to L1
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0); // 0 files in L0
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 1); // 1 file in L1
|
|
|
|
|
|
|
|
metadata.clear();
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
|
|
|
ASSERT_EQ(metadata.size(), 1U);
|
|
|
|
ASSERT_EQ(metadata[0].name /* level1_file.name */, level0_file.name);
|
|
|
|
ASSERT_EQ(metadata[0].size /* level1_file.size */, level0_file.size);
|
|
|
|
|
|
|
|
for (int i = 0; i < num_keys; i++) {
|
|
|
|
ASSERT_EQ(Get(Key(i)), values[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(trivial_move, 1);
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DBTest, TrivialMoveNonOverlappingFiles) {
|
|
|
|
int32_t trivial_move = 0;
|
|
|
|
int32_t non_trivial_move = 0;
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBImpl::BackgroundCompaction:TrivialMove",
|
|
|
|
[&](void* arg) { trivial_move++; });
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBImpl::BackgroundCompaction:NonTrivial",
|
|
|
|
[&](void* arg) { non_trivial_move++; });
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.write_buffer_size = 10 * 1024 * 1024;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
// non overlapping ranges
|
|
|
|
std::vector<std::pair<int32_t, int32_t>> ranges = {
|
|
|
|
{100, 199},
|
|
|
|
{300, 399},
|
|
|
|
{0, 99},
|
|
|
|
{200, 299},
|
|
|
|
{600, 699},
|
|
|
|
{400, 499},
|
|
|
|
{500, 550},
|
|
|
|
{551, 599},
|
|
|
|
};
|
|
|
|
int32_t value_size = 10 * 1024; // 10 KB
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
std::map<int32_t, std::string> values;
|
|
|
|
for (uint32_t i = 0; i < ranges.size(); i++) {
|
|
|
|
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
|
|
|
values[j] = RandomString(&rnd, value_size);
|
|
|
|
ASSERT_OK(Put(Key(j), values[j]));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t level0_files = NumTableFilesAtLevel(0, 0);
|
|
|
|
ASSERT_EQ(level0_files, ranges.size()); // Multiple files in L0
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // No files in L1
|
|
|
|
|
|
|
|
// Since data is non-overlapping we expect compaction to initiate
|
|
|
|
// a trivial move
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
// We expect that all the files were trivially moved from L0 to L1
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 0) /* level1_files */, level0_files);
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < ranges.size(); i++) {
|
|
|
|
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
|
|
|
ASSERT_EQ(Get(Key(j)), values[j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(trivial_move, 1);
|
|
|
|
ASSERT_EQ(non_trivial_move, 0);
|
|
|
|
|
|
|
|
trivial_move = 0;
|
|
|
|
non_trivial_move = 0;
|
|
|
|
values.clear();
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
// Same ranges as above but overlapping
|
|
|
|
ranges = {
|
|
|
|
{100, 199},
|
|
|
|
{300, 399},
|
|
|
|
{0, 99},
|
|
|
|
{200, 299},
|
|
|
|
{600, 699},
|
|
|
|
{400, 499},
|
|
|
|
{500, 560}, // this range overlap with the next one
|
|
|
|
{551, 599},
|
|
|
|
};
|
|
|
|
for (uint32_t i = 0; i < ranges.size(); i++) {
|
|
|
|
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
|
|
|
values[j] = RandomString(&rnd, value_size);
|
|
|
|
ASSERT_OK(Put(Key(j), values[j]));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
|
|
|
|
for (uint32_t i = 0; i < ranges.size(); i++) {
|
|
|
|
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
|
|
|
ASSERT_EQ(Get(Key(j)), values[j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ASSERT_EQ(trivial_move, 0);
|
|
|
|
ASSERT_EQ(non_trivial_move, 1);
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
}
|
|
|
|
|
2015-06-11 23:15:52 +02:00
|
|
|
TEST_F(DBTest, TrivialMoveTargetLevel) {
|
|
|
|
int32_t trivial_move = 0;
|
|
|
|
int32_t non_trivial_move = 0;
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBImpl::BackgroundCompaction:TrivialMove",
|
|
|
|
[&](void* arg) { trivial_move++; });
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBImpl::BackgroundCompaction:NonTrivial",
|
|
|
|
[&](void* arg) { non_trivial_move++; });
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.write_buffer_size = 10 * 1024 * 1024;
|
|
|
|
options.num_levels = 7;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
int32_t value_size = 10 * 1024; // 10 KB
|
|
|
|
|
|
|
|
// Add 2 non-overlapping files
|
|
|
|
Random rnd(301);
|
|
|
|
std::map<int32_t, std::string> values;
|
|
|
|
|
|
|
|
// file 1 [0 => 300]
|
|
|
|
for (int32_t i = 0; i <= 300; i++) {
|
|
|
|
values[i] = RandomString(&rnd, value_size);
|
|
|
|
ASSERT_OK(Put(Key(i), values[i]));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
|
|
|
|
// file 2 [600 => 700]
|
|
|
|
for (int32_t i = 600; i <= 700; i++) {
|
|
|
|
values[i] = RandomString(&rnd, value_size);
|
|
|
|
ASSERT_OK(Put(Key(i), values[i]));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
|
|
|
|
// 2 files in L0
|
|
|
|
ASSERT_EQ("2", FilesPerLevel(0));
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.change_level = true;
|
|
|
|
compact_options.target_level = 6;
|
|
|
|
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
2015-06-11 23:15:52 +02:00
|
|
|
// 2 files in L6
|
|
|
|
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel(0));
|
|
|
|
|
|
|
|
ASSERT_EQ(trivial_move, 1);
|
|
|
|
ASSERT_EQ(non_trivial_move, 0);
|
|
|
|
|
|
|
|
for (int32_t i = 0; i <= 300; i++) {
|
|
|
|
ASSERT_EQ(Get(Key(i)), values[i]);
|
|
|
|
}
|
|
|
|
for (int32_t i = 600; i <= 700; i++) {
|
|
|
|
ASSERT_EQ(Get(Key(i)), values[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionTrigger) {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2012-06-23 04:30:03 +02:00
|
|
|
options.write_buffer_size = 100<<10; //100KB
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-06-23 04:30:03 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger - 1;
|
2012-11-27 06:16:21 +01:00
|
|
|
num++) {
|
2012-06-23 04:30:03 +02:00
|
|
|
std::vector<std::string> values;
|
|
|
|
// Write 120KB (12 values, each 10K)
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
|
|
values.push_back(RandomString(&rnd, 10000));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), values[i]));
|
2012-06-23 04:30:03 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), num + 1);
|
2012-06-23 04:30:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//generate one more file in level-0, and should trigger level-0 compaction
|
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
|
|
values.push_back(RandomString(&rnd, 10000));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), values[i]));
|
2012-06-23 04:30:03 +02:00
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 1);
|
2012-06-23 04:30:03 +02:00
|
|
|
}
|
|
|
|
|
2014-06-25 00:37:06 +02:00
|
|
|
namespace {
|
|
|
|
static const int kCDTValueSize = 1000;
|
|
|
|
static const int kCDTKeysPerBuffer = 4;
|
|
|
|
static const int kCDTNumLevels = 8;
|
|
|
|
Options DeletionTriggerOptions() {
|
|
|
|
Options options;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.write_buffer_size = kCDTKeysPerBuffer * (kCDTValueSize + 24);
|
|
|
|
options.min_write_buffer_number_to_merge = 1;
|
Support saving history in memtable_list
Summary:
For transactions, we are using the memtables to validate that there are no write conflicts. But after flushing, we don't have any memtables, and transactions could fail to commit. So we want to someone keep around some extra history to use for conflict checking. In addition, we want to provide a way to increase the size of this history if too many transactions fail to commit.
After chatting with people, it seems like everyone prefers just using Memtables to store this history (instead of a separate history structure). It seems like the best place for this is abstracted inside the memtable_list. I decide to create a separate list in MemtableListVersion as using the same list complicated the flush/installalflushresults logic too much.
This diff adds a new parameter to control how much memtable history to keep around after flushing. However, it sounds like people aren't too fond of adding new parameters. So I am making the default size of flushed+not-flushed memtables be set to max_write_buffers. This should not change the maximum amount of memory used, but make it more likely we're using closer the the limit. (We are now postponing deleting flushed memtables until the max_write_buffer limit is reached). So while we might use more memory on average, we are still obeying the limit set (and you could argue it's better to go ahead and use up memory now instead of waiting for a write stall to happen to test this limit).
However, if people are opposed to this default behavior, we can easily set it to 0 and require this parameter be set in order to use transactions.
Test Plan: Added a xfunc test to play around with setting different values of this parameter in all tests. Added testing in memtablelist_test and planning on adding more testing here.
Reviewers: sdong, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37443
2015-05-29 01:34:24 +02:00
|
|
|
options.max_write_buffer_number_to_maintain = 0;
|
2014-06-25 00:37:06 +02:00
|
|
|
options.num_levels = kCDTNumLevels;
|
|
|
|
options.max_mem_compaction_level = 0;
|
|
|
|
options.level0_file_num_compaction_trigger = 1;
|
|
|
|
options.target_file_size_base = options.write_buffer_size * 2;
|
|
|
|
options.target_file_size_multiplier = 2;
|
|
|
|
options.max_bytes_for_level_base =
|
|
|
|
options.target_file_size_base * options.target_file_size_multiplier;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
options.disable_auto_compactions = false;
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionDeletionTrigger) {
|
2014-06-25 00:37:06 +02:00
|
|
|
for (int tid = 0; tid < 2; ++tid) {
|
|
|
|
uint64_t db_size[2];
|
2014-11-07 02:28:49 +01:00
|
|
|
Options options = CurrentOptions(DeletionTriggerOptions());
|
|
|
|
|
|
|
|
if (tid == 1) {
|
|
|
|
// second pass with universal compaction
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.num_levels = 1;
|
|
|
|
}
|
2014-06-25 00:37:06 +02:00
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-06-25 00:37:06 +02:00
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
const int kTestSize = kCDTKeysPerBuffer * 512;
|
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int k = 0; k < kTestSize; ++k) {
|
|
|
|
values.push_back(RandomString(&rnd, kCDTValueSize));
|
|
|
|
ASSERT_OK(Put(Key(k), values[k]));
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
db_size[0] = Size(Key(0), Key(kTestSize - 1));
|
|
|
|
|
|
|
|
for (int k = 0; k < kTestSize; ++k) {
|
|
|
|
ASSERT_OK(Delete(Key(k)));
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
db_size[1] = Size(Key(0), Key(kTestSize - 1));
|
|
|
|
|
|
|
|
// must have much smaller db size.
|
|
|
|
ASSERT_GT(db_size[0] / 3, db_size[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionDeletionTriggerReopen) {
|
2014-06-25 00:37:06 +02:00
|
|
|
for (int tid = 0; tid < 2; ++tid) {
|
|
|
|
uint64_t db_size[3];
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions(DeletionTriggerOptions());
|
2014-06-25 00:37:06 +02:00
|
|
|
|
2014-11-07 02:28:49 +01:00
|
|
|
if (tid == 1) {
|
|
|
|
// second pass with universal compaction
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.num_levels = 1;
|
|
|
|
}
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-06-25 00:37:06 +02:00
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
// round 1 --- insert key/value pairs.
|
|
|
|
const int kTestSize = kCDTKeysPerBuffer * 512;
|
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int k = 0; k < kTestSize; ++k) {
|
|
|
|
values.push_back(RandomString(&rnd, kCDTValueSize));
|
|
|
|
ASSERT_OK(Put(Key(k), values[k]));
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
db_size[0] = Size(Key(0), Key(kTestSize - 1));
|
|
|
|
Close();
|
|
|
|
|
|
|
|
// round 2 --- disable auto-compactions and issue deletions.
|
|
|
|
options.create_if_missing = false;
|
|
|
|
options.disable_auto_compactions = true;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-06-25 00:37:06 +02:00
|
|
|
|
|
|
|
for (int k = 0; k < kTestSize; ++k) {
|
|
|
|
ASSERT_OK(Delete(Key(k)));
|
|
|
|
}
|
|
|
|
db_size[1] = Size(Key(0), Key(kTestSize - 1));
|
|
|
|
Close();
|
|
|
|
// as auto_compaction is off, we shouldn't see too much reduce
|
|
|
|
// in db size.
|
|
|
|
ASSERT_LT(db_size[0] / 3, db_size[1]);
|
|
|
|
|
|
|
|
// round 3 --- reopen db with auto_compaction on and see if
|
|
|
|
// deletion compensation still work.
|
|
|
|
options.disable_auto_compactions = false;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-06-25 00:37:06 +02:00
|
|
|
// insert relatively small amount of data to trigger auto compaction.
|
|
|
|
for (int k = 0; k < kTestSize / 10; ++k) {
|
|
|
|
ASSERT_OK(Put(Key(k), values[k]));
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
db_size[2] = Size(Key(0), Key(kTestSize - 1));
|
|
|
|
// this time we're expecting significant drop in size.
|
|
|
|
ASSERT_GT(db_size[0] / 3, db_size[2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-20 00:20:29 +01:00
|
|
|
// This is a static filter used for filtering
|
|
|
|
// kvs during the compaction process.
|
|
|
|
static int cfilter_count;
|
|
|
|
static std::string NEW_VALUE = "NewValue";
|
|
|
|
|
|
|
|
class KeepFilter : public CompactionFilter {
|
|
|
|
public:
|
|
|
|
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
|
|
|
std::string* new_value, bool* value_changed) const
|
|
|
|
override {
|
|
|
|
cfilter_count++;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "KeepFilter"; }
|
|
|
|
};
|
|
|
|
|
|
|
|
class DeleteFilter : public CompactionFilter {
|
|
|
|
public:
|
|
|
|
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
|
|
|
std::string* new_value, bool* value_changed) const
|
|
|
|
override {
|
|
|
|
cfilter_count++;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "DeleteFilter"; }
|
|
|
|
};
|
|
|
|
|
2015-03-03 19:59:36 +01:00
|
|
|
class DelayFilter : public CompactionFilter {
|
|
|
|
public:
|
|
|
|
explicit DelayFilter(DBTest* d) : db_test(d) {}
|
|
|
|
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
|
|
|
std::string* new_value,
|
|
|
|
bool* value_changed) const override {
|
2015-05-16 00:52:51 +02:00
|
|
|
db_test->env_->addon_time_.fetch_add(1000);
|
2015-03-03 19:59:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "DelayFilter"; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
DBTest* db_test;
|
|
|
|
};
|
|
|
|
|
2014-11-14 00:19:57 +01:00
|
|
|
class ConditionalFilter : public CompactionFilter {
|
|
|
|
public:
|
|
|
|
explicit ConditionalFilter(const std::string* filtered_value)
|
|
|
|
: filtered_value_(filtered_value) {}
|
|
|
|
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
|
|
|
std::string* new_value,
|
|
|
|
bool* value_changed) const override {
|
|
|
|
return value.ToString() == *filtered_value_;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "ConditionalFilter"; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
const std::string* filtered_value_;
|
|
|
|
};
|
|
|
|
|
2014-03-20 00:20:29 +01:00
|
|
|
class ChangeFilter : public CompactionFilter {
|
|
|
|
public:
|
|
|
|
explicit ChangeFilter() {}
|
|
|
|
|
|
|
|
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
|
|
|
std::string* new_value, bool* value_changed) const
|
|
|
|
override {
|
|
|
|
assert(new_value != nullptr);
|
|
|
|
*new_value = NEW_VALUE;
|
|
|
|
*value_changed = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "ChangeFilter"; }
|
|
|
|
};
|
|
|
|
|
|
|
|
class KeepFilterFactory : public CompactionFilterFactory {
|
|
|
|
public:
|
|
|
|
explicit KeepFilterFactory(bool check_context = false)
|
|
|
|
: check_context_(check_context) {}
|
|
|
|
|
|
|
|
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
2014-04-02 23:48:53 +02:00
|
|
|
const CompactionFilter::Context& context) override {
|
2014-03-20 00:20:29 +01:00
|
|
|
if (check_context_) {
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction);
|
|
|
|
EXPECT_EQ(expect_manual_compaction_.load(), context.is_manual_compaction);
|
2014-03-20 00:20:29 +01:00
|
|
|
}
|
|
|
|
return std::unique_ptr<CompactionFilter>(new KeepFilter());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "KeepFilterFactory"; }
|
|
|
|
bool check_context_;
|
|
|
|
std::atomic_bool expect_full_compaction_;
|
|
|
|
std::atomic_bool expect_manual_compaction_;
|
|
|
|
};
|
|
|
|
|
|
|
|
class DeleteFilterFactory : public CompactionFilterFactory {
|
|
|
|
public:
|
|
|
|
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
2014-04-02 23:48:53 +02:00
|
|
|
const CompactionFilter::Context& context) override {
|
2014-03-20 00:20:29 +01:00
|
|
|
if (context.is_manual_compaction) {
|
|
|
|
return std::unique_ptr<CompactionFilter>(new DeleteFilter());
|
|
|
|
} else {
|
|
|
|
return std::unique_ptr<CompactionFilter>(nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "DeleteFilterFactory"; }
|
|
|
|
};
|
|
|
|
|
2015-03-03 19:59:36 +01:00
|
|
|
class DelayFilterFactory : public CompactionFilterFactory {
|
|
|
|
public:
|
|
|
|
explicit DelayFilterFactory(DBTest* d) : db_test(d) {}
|
|
|
|
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
|
|
|
const CompactionFilter::Context& context) override {
|
|
|
|
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "DelayFilterFactory"; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
DBTest* db_test;
|
|
|
|
};
|
|
|
|
|
2014-11-14 00:19:57 +01:00
|
|
|
class ConditionalFilterFactory : public CompactionFilterFactory {
|
|
|
|
public:
|
|
|
|
explicit ConditionalFilterFactory(const Slice& filtered_value)
|
|
|
|
: filtered_value_(filtered_value.ToString()) {}
|
|
|
|
|
|
|
|
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
|
|
|
const CompactionFilter::Context& context) override {
|
|
|
|
return std::unique_ptr<CompactionFilter>(
|
|
|
|
new ConditionalFilter(&filtered_value_));
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "ConditionalFilterFactory";
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string filtered_value_;
|
|
|
|
};
|
|
|
|
|
2014-03-20 00:20:29 +01:00
|
|
|
class ChangeFilterFactory : public CompactionFilterFactory {
|
|
|
|
public:
|
|
|
|
explicit ChangeFilterFactory() {}
|
|
|
|
|
|
|
|
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
2014-04-02 23:48:53 +02:00
|
|
|
const CompactionFilter::Context& context) override {
|
2014-03-20 00:20:29 +01:00
|
|
|
return std::unique_ptr<CompactionFilter>(new ChangeFilter());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "ChangeFilterFactory"; }
|
|
|
|
};
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
class DBTestUniversalCompactionBase
|
|
|
|
: public DBTest,
|
|
|
|
public ::testing::WithParamInterface<int> {
|
|
|
|
public:
|
|
|
|
virtual void SetUp() override { num_levels_ = GetParam(); }
|
|
|
|
int num_levels_;
|
|
|
|
};
|
|
|
|
|
|
|
|
class DBTestUniversalCompaction : public DBTestUniversalCompactionBase {};
|
|
|
|
|
2014-03-13 00:40:14 +01:00
|
|
|
// TODO(kailiu) The tests on UniversalCompaction has some issues:
|
|
|
|
// 1. A lot of magic numbers ("11" or "12").
|
2014-12-02 21:09:20 +01:00
|
|
|
// 2. Made assumption on the memtable flush conditions, which may change from
|
2014-03-13 00:40:14 +01:00
|
|
|
// time to time.
|
2015-03-30 23:04:21 +02:00
|
|
|
TEST_P(DBTestUniversalCompaction, UniversalCompactionTrigger) {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-08-08 00:20:41 +02:00
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = num_levels_;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.target_file_size_base = 32 << 10; // 32KB
|
2013-09-17 22:56:30 +02:00
|
|
|
// trigger compaction if there are >= 4 files
|
|
|
|
options.level0_file_num_compaction_trigger = 4;
|
2014-03-20 00:20:29 +01:00
|
|
|
KeepFilterFactory* filter = new KeepFilterFactory(true);
|
|
|
|
filter->expect_manual_compaction_.store(false);
|
|
|
|
options.compaction_filter_factory.reset(filter);
|
|
|
|
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2015-03-30 23:04:21 +02:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
2015-05-13 19:29:49 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBTestWritableFile.GetPreallocationStatus", [&](void* arg) {
|
|
|
|
ASSERT_TRUE(arg != nullptr);
|
|
|
|
size_t preallocation_size = *(static_cast<size_t*>(arg));
|
|
|
|
if (num_levels_ > 3) {
|
|
|
|
ASSERT_LE(preallocation_size, options.target_file_size_base * 1.1);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
2014-03-20 00:20:29 +01:00
|
|
|
filter->expect_full_compaction_.store(true);
|
2013-08-08 00:20:41 +02:00
|
|
|
// Stage 1:
|
|
|
|
// Generate a set of files at level 0, but don't trigger level-0
|
|
|
|
// compaction.
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger - 1;
|
2013-08-08 00:20:41 +02:00
|
|
|
num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < 12; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), num + 1);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate one more file at level-0, which should trigger level-0
|
|
|
|
// compaction.
|
2014-03-13 00:40:14 +01:00
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Suppose each file flushed from mem table has size 1. Now we compact
|
|
|
|
// (level0_file_num_compaction_trigger+1)=4 files and should have a big
|
|
|
|
// file of size 4.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 1);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Stage 2:
|
|
|
|
// Now we have one file at level 0, with size 4. We also have some data in
|
|
|
|
// mem table. Let's continue generating new files at level 0, but don't
|
|
|
|
// trigger level-0 compaction.
|
|
|
|
// First, clean up memtable before inserting new data. This will generate
|
|
|
|
// a level-0 file, with size around 0.4 (according to previously written
|
|
|
|
// data amount).
|
2014-03-20 17:52:38 +01:00
|
|
|
filter->expect_full_compaction_.store(false);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger - 3;
|
2013-08-08 00:20:41 +02:00
|
|
|
num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), num + 3);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate one more file at level-0, which should trigger level-0
|
|
|
|
// compaction.
|
2014-03-13 00:40:14 +01:00
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Before compaction, we have 4 files at level 0, with size 4, 0.4, 1, 1.
|
2014-12-02 21:09:20 +01:00
|
|
|
// After compaction, we should have 2 files, with size 4, 2.4.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 2);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Stage 3:
|
|
|
|
// Now we have 2 files at level 0, with size 4 and 2.4. Continue
|
|
|
|
// generating new files at level 0.
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger - 3;
|
2013-08-08 00:20:41 +02:00
|
|
|
num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), num + 3);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate one more file at level-0, which should trigger level-0
|
|
|
|
// compaction.
|
|
|
|
for (int i = 0; i < 12; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Before compaction, we have 4 files at level 0, with size 4, 2.4, 1, 1.
|
2014-12-02 21:09:20 +01:00
|
|
|
// After compaction, we should have 3 files, with size 4, 2.4, 2.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 3);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Stage 4:
|
|
|
|
// Now we have 3 files at level 0, with size 4, 2.4, 2. Let's generate a
|
|
|
|
// new file of size 1.
|
2014-03-13 00:40:14 +01:00
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Level-0 compaction is triggered, but no file will be picked up.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 4);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Stage 5:
|
|
|
|
// Now we have 4 files at level 0, with size 4, 2.4, 2, 1. Let's generate
|
|
|
|
// a new file of size 1.
|
2014-03-20 00:20:29 +01:00
|
|
|
filter->expect_full_compaction_.store(true);
|
2014-03-13 00:40:14 +01:00
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-08-08 00:20:41 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// All files at level 0 will be compacted into a single one.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 1);
|
2015-05-13 19:29:49 +02:00
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
TEST_P(DBTestUniversalCompaction, UniversalCompactionSizeAmplification) {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-09-10 01:06:10 +02:00
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = num_levels_;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.target_file_size_base = 32 << 10; // 32KB
|
2013-09-18 05:02:54 +02:00
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
2014-10-31 23:08:10 +01:00
|
|
|
options = CurrentOptions(options);
|
2015-03-30 23:04:21 +02:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-09-10 01:06:10 +02:00
|
|
|
|
|
|
|
// Trigger compaction if size amplification exceeds 110%
|
2014-02-07 23:47:16 +01:00
|
|
|
options.compaction_options_universal.max_size_amplification_percent = 110;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2013-09-10 01:06:10 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// Generate two files in Level 0. Both files are approx the same size.
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger - 1;
|
2013-09-10 01:06:10 +02:00
|
|
|
num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-09-10 01:06:10 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), num + 1);
|
2013-09-10 01:06:10 +02:00
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 2);
|
2013-09-10 01:06:10 +02:00
|
|
|
|
|
|
|
// Flush whatever is remaining in memtable. This is typically
|
|
|
|
// small, which should not trigger size ratio based compaction
|
|
|
|
// but will instead trigger size amplification.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2013-09-10 01:06:10 +02:00
|
|
|
|
2013-10-16 22:32:53 +02:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
2013-09-10 01:06:10 +02:00
|
|
|
// Verify that size amplification did occur
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 1);
|
2013-09-10 01:06:10 +02:00
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
class DBTestUniversalCompactionMultiLevels
|
|
|
|
: public DBTestUniversalCompactionBase {};
|
|
|
|
|
|
|
|
TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionMultiLevels) {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-10-03 01:20:17 +02:00
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = num_levels_;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 8;
|
|
|
|
options.max_background_compactions = 3;
|
|
|
|
options.target_file_size_base = 32 * 1024;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
// Trigger compaction if size amplification exceeds 110%
|
|
|
|
options.compaction_options_universal.max_size_amplification_percent = 110;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int num_keys = 100000;
|
|
|
|
for (int i = 0; i < num_keys * 2; i++) {
|
|
|
|
ASSERT_OK(Put(1, Key(i % num_keys), Key(i)));
|
|
|
|
}
|
|
|
|
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
for (int i = num_keys; i < num_keys * 2; i++) {
|
|
|
|
ASSERT_EQ(Get(1, Key(i % num_keys)), Key(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(DBTestUniversalCompactionMultiLevels,
|
|
|
|
DBTestUniversalCompactionMultiLevels,
|
|
|
|
::testing::Values(3, 20));
|
|
|
|
|
|
|
|
class DBTestUniversalCompactionParallel : public DBTestUniversalCompactionBase {
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) {
|
|
|
|
Options options;
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.num_levels = num_levels_;
|
|
|
|
options.write_buffer_size = 1 << 10; // 1KB
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
|
|
|
options.max_background_compactions = 3;
|
|
|
|
options.max_background_flushes = 3;
|
|
|
|
options.target_file_size_base = 1 * 1024;
|
|
|
|
options.compaction_options_universal.max_size_amplification_percent = 110;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
// Delay every compaction so multiple compactions will happen.
|
|
|
|
std::atomic<int> num_compactions_running(0);
|
|
|
|
std::atomic<bool> has_parallel(false);
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack("CompactionJob::Run():Start",
|
2015-04-14 10:55:19 +02:00
|
|
|
[&](void* arg) {
|
2015-03-30 23:04:21 +02:00
|
|
|
if (num_compactions_running.fetch_add(1) > 0) {
|
|
|
|
has_parallel.store(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (int nwait = 0; nwait < 20000; nwait++) {
|
|
|
|
if (has_parallel.load() || num_compactions_running.load() > 1) {
|
|
|
|
has_parallel.store(true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
env_->SleepForMicroseconds(1000);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"CompactionJob::Run():End",
|
2015-04-14 10:55:19 +02:00
|
|
|
[&](void* arg) { num_compactions_running.fetch_add(-1); });
|
2015-03-30 23:04:21 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int num_keys = 30000;
|
|
|
|
for (int i = 0; i < num_keys * 2; i++) {
|
|
|
|
ASSERT_OK(Put(1, Key(i % num_keys), Key(i)));
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
ASSERT_EQ(num_compactions_running.load(), 0);
|
|
|
|
ASSERT_TRUE(has_parallel.load());
|
|
|
|
|
|
|
|
for (int i = num_keys; i < num_keys * 2; i++) {
|
|
|
|
ASSERT_EQ(Get(1, Key(i % num_keys)), Key(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reopen and check.
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
for (int i = num_keys; i < num_keys * 2; i++) {
|
|
|
|
ASSERT_EQ(Get(1, Key(i % num_keys)), Key(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(DBTestUniversalCompactionParallel,
|
|
|
|
DBTestUniversalCompactionParallel,
|
|
|
|
::testing::Values(1, 10));
|
|
|
|
|
|
|
|
TEST_P(DBTestUniversalCompaction, UniversalCompactionOptions) {
|
|
|
|
Options options;
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.target_file_size_base = 32 << 10; // 32KB
|
2013-10-03 01:20:17 +02:00
|
|
|
options.level0_file_num_compaction_trigger = 4;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = num_levels_;
|
2013-10-17 22:33:39 +02:00
|
|
|
options.compaction_options_universal.compression_size_percent = -1;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2015-03-30 23:04:21 +02:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-10-03 01:20:17 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
2013-10-03 01:20:17 +02:00
|
|
|
key_idx++;
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
2013-10-03 01:20:17 +02:00
|
|
|
|
|
|
|
if (num < options.level0_file_num_compaction_trigger - 1) {
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), num + 1);
|
2013-10-03 01:20:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(1), 1);
|
2013-10-03 01:20:17 +02:00
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
TEST_P(DBTestUniversalCompaction, UniversalCompactionStopStyleSimilarSize) {
|
2013-12-24 10:09:09 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.target_file_size_base = 32 << 10; // 32KB
|
2013-12-24 10:09:09 +01:00
|
|
|
// trigger compaction if there are >= 4 files
|
|
|
|
options.level0_file_num_compaction_trigger = 4;
|
|
|
|
options.compaction_options_universal.size_ratio = 10;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.compaction_options_universal.stop_style =
|
|
|
|
kCompactionStopStyleSimilarSize;
|
|
|
|
options.num_levels = num_levels_;
|
|
|
|
DestroyAndReopen(options);
|
2013-12-24 10:09:09 +01:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// Stage 1:
|
|
|
|
// Generate a set of files at level 0, but don't trigger level-0
|
|
|
|
// compaction.
|
2015-03-30 23:04:21 +02:00
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger - 1;
|
2013-12-24 10:09:09 +01:00
|
|
|
num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-12-24 10:09:09 +01:00
|
|
|
ASSERT_OK(Put(Key(key_idx), RandomString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(), num + 1);
|
2013-12-24 10:09:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate one more file at level-0, which should trigger level-0
|
|
|
|
// compaction.
|
2014-03-13 00:40:14 +01:00
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-12-24 10:09:09 +01:00
|
|
|
ASSERT_OK(Put(Key(key_idx), RandomString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Suppose each file flushed from mem table has size 1. Now we compact
|
|
|
|
// (level0_file_num_compaction_trigger+1)=4 files and should have a big
|
|
|
|
// file of size 4.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(), 1);
|
2013-12-24 10:09:09 +01:00
|
|
|
|
|
|
|
// Stage 2:
|
|
|
|
// Now we have one file at level 0, with size 4. We also have some data in
|
|
|
|
// mem table. Let's continue generating new files at level 0, but don't
|
|
|
|
// trigger level-0 compaction.
|
|
|
|
// First, clean up memtable before inserting new data. This will generate
|
|
|
|
// a level-0 file, with size around 0.4 (according to previously written
|
|
|
|
// data amount).
|
|
|
|
dbfull()->Flush(FlushOptions());
|
2015-03-30 23:04:21 +02:00
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger - 3;
|
2013-12-24 10:09:09 +01:00
|
|
|
num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-12-24 10:09:09 +01:00
|
|
|
ASSERT_OK(Put(Key(key_idx), RandomString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(), num + 3);
|
2013-12-24 10:09:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate one more file at level-0, which should trigger level-0
|
|
|
|
// compaction.
|
2014-03-13 00:40:14 +01:00
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-12-24 10:09:09 +01:00
|
|
|
ASSERT_OK(Put(Key(key_idx), RandomString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Before compaction, we have 4 files at level 0, with size 4, 0.4, 1, 1.
|
|
|
|
// After compaction, we should have 3 files, with size 4, 0.4, 2.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(), 3);
|
2013-12-24 10:09:09 +01:00
|
|
|
// Stage 3:
|
|
|
|
// Now we have 3 files at level 0, with size 4, 0.4, 2. Generate one
|
|
|
|
// more file at level-0, which should trigger level-0 compaction.
|
2014-03-13 00:40:14 +01:00
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-12-24 10:09:09 +01:00
|
|
|
ASSERT_OK(Put(Key(key_idx), RandomString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Level-0 compaction is triggered, but no file will be picked up.
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(), 4);
|
2013-12-24 10:09:09 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompressedCache) {
|
2015-04-06 21:50:44 +02:00
|
|
|
if (!Snappy_Supported()) {
|
2015-03-13 19:08:50 +01:00
|
|
|
return;
|
|
|
|
}
|
2013-11-27 22:32:56 +01:00
|
|
|
int num_iter = 80;
|
|
|
|
|
|
|
|
// Run this test three iterations.
|
|
|
|
// Iteration 1: only a uncompressed block cache
|
|
|
|
// Iteration 2: only a compressed block cache
|
|
|
|
// Iteration 3: both block cache and compressed cache
|
2014-04-09 20:43:14 +02:00
|
|
|
// Iteration 4: both block cache and compressed cache, but DB is not
|
|
|
|
// compressed
|
|
|
|
for (int iter = 0; iter < 4; iter++) {
|
2014-08-25 23:22:05 +02:00
|
|
|
Options options;
|
2013-11-27 22:32:56 +01:00
|
|
|
options.write_buffer_size = 64*1024; // small write buffer
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
2014-10-31 23:08:10 +01:00
|
|
|
options = CurrentOptions(options);
|
2013-11-27 22:32:56 +01:00
|
|
|
|
2014-08-25 23:22:05 +02:00
|
|
|
BlockBasedTableOptions table_options;
|
2013-11-27 22:32:56 +01:00
|
|
|
switch (iter) {
|
|
|
|
case 0:
|
|
|
|
// only uncompressed block cache
|
2014-08-25 23:22:05 +02:00
|
|
|
table_options.block_cache = NewLRUCache(8*1024);
|
|
|
|
table_options.block_cache_compressed = nullptr;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
2013-11-27 22:32:56 +01:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
// no block cache, only compressed cache
|
2014-08-25 23:22:05 +02:00
|
|
|
table_options.no_block_cache = true;
|
|
|
|
table_options.block_cache = nullptr;
|
|
|
|
table_options.block_cache_compressed = NewLRUCache(8*1024);
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
2013-11-27 22:32:56 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
// both compressed and uncompressed block cache
|
2014-08-25 23:22:05 +02:00
|
|
|
table_options.block_cache = NewLRUCache(1024);
|
|
|
|
table_options.block_cache_compressed = NewLRUCache(8*1024);
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
2013-11-27 22:32:56 +01:00
|
|
|
break;
|
2014-04-09 20:43:14 +02:00
|
|
|
case 3:
|
|
|
|
// both block cache and compressed cache, but DB is not compressed
|
|
|
|
// also, make block cache sizes bigger, to trigger block cache hits
|
2014-08-25 23:22:05 +02:00
|
|
|
table_options.block_cache = NewLRUCache(1024 * 1024);
|
|
|
|
table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
2014-04-09 20:43:14 +02:00
|
|
|
options.compression = kNoCompression;
|
|
|
|
break;
|
2013-11-27 22:32:56 +01:00
|
|
|
default:
|
|
|
|
ASSERT_TRUE(false);
|
|
|
|
}
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-15 02:47:53 +01:00
|
|
|
// default column family doesn't have block cache
|
|
|
|
Options no_block_cache_opts;
|
|
|
|
no_block_cache_opts.statistics = options.statistics;
|
2014-10-31 23:08:10 +01:00
|
|
|
no_block_cache_opts = CurrentOptions(no_block_cache_opts);
|
2014-08-25 23:22:05 +02:00
|
|
|
BlockBasedTableOptions table_options_no_bc;
|
|
|
|
table_options_no_bc.no_block_cache = true;
|
|
|
|
no_block_cache_opts.table_factory.reset(
|
|
|
|
NewBlockBasedTableFactory(table_options_no_bc));
|
2014-02-15 02:47:53 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"},
|
2014-10-29 20:00:42 +01:00
|
|
|
std::vector<Options>({no_block_cache_opts, options}));
|
2013-11-27 22:32:56 +01:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
// Write 8MB (80 values, each 100K)
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
2013-11-27 22:32:56 +01:00
|
|
|
std::vector<std::string> values;
|
|
|
|
std::string str;
|
|
|
|
for (int i = 0; i < num_iter; i++) {
|
|
|
|
if (i % 4 == 0) { // high compression ratio
|
|
|
|
str = RandomString(&rnd, 1000);
|
|
|
|
}
|
|
|
|
values.push_back(str);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), values[i]));
|
2013-11-27 22:32:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// flush all data from memtable so that reads are from block cache
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2013-11-27 22:32:56 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < num_iter; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Get(1, Key(i)), values[i]);
|
2013-11-27 22:32:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// check that we triggered the appropriate code paths in the cache
|
|
|
|
switch (iter) {
|
|
|
|
case 0:
|
|
|
|
// only uncompressed block cache
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
2013-11-27 22:32:56 +01:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
// no block cache, only compressed cache
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
2013-11-27 22:32:56 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
// both compressed and uncompressed block cache
|
2014-01-17 21:46:06 +01:00
|
|
|
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
2013-11-27 22:32:56 +01:00
|
|
|
break;
|
2014-04-09 20:43:14 +02:00
|
|
|
case 3:
|
|
|
|
// both compressed and uncompressed block cache
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_HIT), 0);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
|
|
|
// compressed doesn't have any hits since blocks are not compressed on
|
|
|
|
// storage
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT), 0);
|
|
|
|
break;
|
2013-11-27 22:32:56 +01:00
|
|
|
default:
|
|
|
|
ASSERT_TRUE(false);
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
|
|
|
|
options.create_if_missing = true;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2013-11-27 22:32:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-17 08:44:39 +01:00
|
|
|
static std::string CompressibleString(Random* rnd, int len) {
|
|
|
|
std::string r;
|
|
|
|
test::CompressibleString(rnd, 0.8, len, &r);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio1) {
|
2015-04-06 21:50:44 +02:00
|
|
|
if (!Snappy_Supported()) {
|
2015-03-13 19:08:50 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-10-17 22:33:39 +02:00
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.target_file_size_base = 32 << 10; // 32KB
|
2013-10-17 22:33:39 +02:00
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = num_levels_;
|
2013-10-17 22:33:39 +02:00
|
|
|
options.compaction_options_universal.compression_size_percent = 70;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2015-03-30 23:04:21 +02:00
|
|
|
DestroyAndReopen(options);
|
2013-10-17 22:33:39 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// The first compaction (2) is compressed.
|
|
|
|
for (int num = 0; num < 2; num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-10-17 22:33:39 +02:00
|
|
|
ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_LT(TotalSize(), 110000U * 2 * 0.9);
|
2013-10-17 22:33:39 +02:00
|
|
|
|
|
|
|
// The second compaction (4) is compressed
|
|
|
|
for (int num = 0; num < 2; num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-10-17 22:33:39 +02:00
|
|
|
ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_LT(TotalSize(), 110000 * 4 * 0.9);
|
2013-10-17 22:33:39 +02:00
|
|
|
|
|
|
|
// The third compaction (2 4) is compressed since this time it is
|
|
|
|
// (1 1 3.2) and 3.2/5.2 doesn't reach ratio.
|
|
|
|
for (int num = 0; num < 2; num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-10-17 22:33:39 +02:00
|
|
|
ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_LT(TotalSize(), 110000 * 6 * 0.9);
|
2013-10-17 22:33:39 +02:00
|
|
|
|
|
|
|
// When we start for the compaction up to (2 4 8), the latest
|
|
|
|
// compressed is not compressed.
|
|
|
|
for (int num = 0; num < 8; num++) {
|
2014-03-13 00:40:14 +01:00
|
|
|
// Write 110KB (11 values, each 10K)
|
|
|
|
for (int i = 0; i < 11; i++) {
|
2013-10-17 22:33:39 +02:00
|
|
|
ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_GT(TotalSize(), 110000 * 11 * 0.8 + 110000 * 2);
|
2013-10-17 22:33:39 +02:00
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio2) {
|
2015-04-06 21:50:44 +02:00
|
|
|
if (!Snappy_Supported()) {
|
2015-03-13 19:08:50 +01:00
|
|
|
return;
|
|
|
|
}
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-10-17 22:33:39 +02:00
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.target_file_size_base = 32 << 10; // 32KB
|
2013-10-17 22:33:39 +02:00
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = num_levels_;
|
2013-10-17 22:33:39 +02:00
|
|
|
options.compaction_options_universal.compression_size_percent = 95;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2015-03-30 23:04:21 +02:00
|
|
|
DestroyAndReopen(options);
|
2013-10-17 22:33:39 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// When we start for the compaction up to (2 4 8), the latest
|
|
|
|
// compressed is compressed given the size ratio to compress.
|
|
|
|
for (int num = 0; num < 14; num++) {
|
|
|
|
// Write 120KB (12 values, each 10K)
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
|
|
ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000)));
|
|
|
|
key_idx++;
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_LT(TotalSize(), 120000U * 12 * 0.8 + 120000 * 2);
|
2013-10-17 22:33:39 +02:00
|
|
|
}
|
2014-07-02 18:54:20 +02:00
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
INSTANTIATE_TEST_CASE_P(UniversalCompactionNumLevels, DBTestUniversalCompaction,
|
|
|
|
::testing::Values(1, 3, 5));
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FailMoreDbPaths) {
|
2015-01-29 00:30:02 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-07-15 00:34:30 +02:00
|
|
|
options.db_paths.emplace_back(dbname_, 10000000);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 1000000);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_3", 1000000);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_4", 1000000);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_5", 1000000);
|
2014-10-29 20:00:01 +01:00
|
|
|
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
2014-07-02 18:54:20 +02:00
|
|
|
}
|
2014-07-15 00:34:30 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, UniversalCompactionSecondPathRatio) {
|
2015-04-06 21:50:44 +02:00
|
|
|
if (!Snappy_Supported()) {
|
2015-03-13 19:08:50 +01:00
|
|
|
return;
|
|
|
|
}
|
2014-07-15 00:34:30 +02:00
|
|
|
Options options;
|
|
|
|
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 1024 * 1024 * 1024);
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.num_levels = 1;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
|
|
|
|
std::vector<std::string> filenames;
|
|
|
|
env_->GetChildren(options.db_paths[1].path, &filenames);
|
|
|
|
// Delete archival files.
|
|
|
|
for (size_t i = 0; i < filenames.size(); ++i) {
|
|
|
|
env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
|
|
|
|
}
|
|
|
|
env_->DeleteDir(options.db_paths[1].path);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-07-15 00:34:30 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// First three 110KB files are not going to second path.
|
|
|
|
// After that, (100K, 200K)
|
|
|
|
for (int num = 0; num < 3; num++) {
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Another 110KB triggers a compaction to 400K file to second path
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
// (1, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1,1,4) -> (2, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 2, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(2, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 1, 2, 4) -> (8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 1, 8) -> (2, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 2, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(2, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 1, 2, 8) -> (4, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-07-15 00:34:30 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
2014-07-15 00:34:30 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, LevelCompactionThirdPath) {
|
2015-01-29 00:30:02 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-12-16 06:48:16 +01:00
|
|
|
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024);
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.max_bytes_for_level_base = 400 * 1024;
|
|
|
|
// options = CurrentOptions(options);
|
|
|
|
|
|
|
|
std::vector<std::string> filenames;
|
|
|
|
env_->GetChildren(options.db_paths[1].path, &filenames);
|
|
|
|
// Delete archival files.
|
|
|
|
for (size_t i = 0; i < filenames.size(); ++i) {
|
|
|
|
env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
|
|
|
|
}
|
|
|
|
env_->DeleteDir(options.db_paths[1].path);
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// First three 110KB files are not going to second path.
|
|
|
|
// After that, (100K, 200K)
|
|
|
|
for (int num = 0; num < 3; num++) {
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Another 110KB triggers a compaction to 400K file to fill up first path
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(3, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
// (1, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 1)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 2)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,2", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(2, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 3)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,3", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(3, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,4", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 5)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,5", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(5, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 6)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,6", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(6, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 7)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,7", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(7, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,8", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(8, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
Destroy(options);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, LevelCompactionPathUse) {
|
2015-01-29 00:30:02 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-12-16 06:48:16 +01:00
|
|
|
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024);
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.max_bytes_for_level_base = 400 * 1024;
|
|
|
|
// options = CurrentOptions(options);
|
|
|
|
|
|
|
|
std::vector<std::string> filenames;
|
|
|
|
env_->GetChildren(options.db_paths[1].path, &filenames);
|
|
|
|
// Delete archival files.
|
|
|
|
for (size_t i = 0; i < filenames.size(); ++i) {
|
|
|
|
env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
|
|
|
|
}
|
|
|
|
env_->DeleteDir(options.db_paths[1].path);
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// Always gets compacted into 1 Level1 file,
|
|
|
|
// 0/1 Level 0 file
|
|
|
|
for (int num = 0; num < 3; num++) {
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
key_idx = 0;
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,1", FilesPerLevel(0));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
Destroy(options);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, UniversalCompactionFourPaths) {
|
2014-07-15 00:34:30 +02:00
|
|
|
Options options;
|
|
|
|
options.db_paths.emplace_back(dbname_, 300 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 300 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_4", 1024 * 1024 * 1024);
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.num_levels = 1;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
|
|
|
|
std::vector<std::string> filenames;
|
|
|
|
env_->GetChildren(options.db_paths[1].path, &filenames);
|
|
|
|
// Delete archival files.
|
|
|
|
for (size_t i = 0; i < filenames.size(); ++i) {
|
|
|
|
env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
|
|
|
|
}
|
|
|
|
env_->DeleteDir(options.db_paths[1].path);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-07-15 00:34:30 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// First three 110KB files are not going to second path.
|
|
|
|
// After that, (100K, 200K)
|
|
|
|
for (int num = 0; num < 3; num++) {
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Another 110KB triggers a compaction to 400K file to second path
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
|
|
|
|
// (1, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1,1,4) -> (2, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 2, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 1, 2, 4) -> (8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
|
|
|
|
|
|
|
// (1, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 1, 8) -> (2, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
// (1, 2, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 1, 2, 8) -> (4, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
|
|
|
|
|
|
|
// (1, 4, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-07-15 00:34:30 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
2014-07-15 00:34:30 +02:00
|
|
|
}
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
|
|
|
|
void CheckColumnFamilyMeta(const ColumnFamilyMetaData& cf_meta) {
|
|
|
|
uint64_t cf_size = 0;
|
|
|
|
uint64_t cf_csize = 0;
|
|
|
|
size_t file_count = 0;
|
|
|
|
for (auto level_meta : cf_meta.levels) {
|
|
|
|
uint64_t level_size = 0;
|
|
|
|
uint64_t level_csize = 0;
|
|
|
|
file_count += level_meta.files.size();
|
|
|
|
for (auto file_meta : level_meta.files) {
|
|
|
|
level_size += file_meta.size;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(level_meta.size, level_size);
|
|
|
|
cf_size += level_size;
|
|
|
|
cf_csize += level_csize;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(cf_meta.file_count, file_count);
|
|
|
|
ASSERT_EQ(cf_meta.size, cf_size);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ColumnFamilyMetaDataTest) {
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_index = 0;
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
for (int i = 0; i < 100; ++i) {
|
|
|
|
GenerateNewFile(&rnd, &key_index);
|
|
|
|
db_->GetColumnFamilyMetaData(&cf_meta);
|
|
|
|
CheckColumnFamilyMeta(cf_meta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ConvertCompactionStyle) {
|
2013-09-04 22:13:08 +02:00
|
|
|
Random rnd(301);
|
|
|
|
int max_key_level_insert = 200;
|
|
|
|
int max_key_universal_insert = 600;
|
|
|
|
|
|
|
|
// Stage 1: generate a db with level compaction
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-09-04 22:13:08 +02:00
|
|
|
options.write_buffer_size = 100<<10; //100KB
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
|
|
|
options.max_bytes_for_level_base = 500<<10; // 500KB
|
|
|
|
options.max_bytes_for_level_multiplier = 1;
|
|
|
|
options.target_file_size_base = 200<<10; // 200KB
|
|
|
|
options.target_file_size_multiplier = 1;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-09-04 22:13:08 +02:00
|
|
|
|
|
|
|
for (int i = 0; i <= max_key_level_insert; i++) {
|
|
|
|
// each value is 10K
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
2013-09-04 22:13:08 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2013-09-04 22:13:08 +02:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_GT(TotalTableFiles(1, 4), 1);
|
2013-09-04 22:13:08 +02:00
|
|
|
int non_level0_num_files = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int i = 1; i < options.num_levels; i++) {
|
|
|
|
non_level0_num_files += NumTableFilesAtLevel(i, 1);
|
2013-09-04 22:13:08 +02:00
|
|
|
}
|
|
|
|
ASSERT_GT(non_level0_num_files, 0);
|
|
|
|
|
|
|
|
// Stage 2: reopen with universal compaction - should fail
|
|
|
|
options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = 1;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:42 +01:00
|
|
|
Status s = TryReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2013-09-04 22:13:08 +02:00
|
|
|
ASSERT_TRUE(s.IsInvalidArgument());
|
|
|
|
|
|
|
|
// Stage 3: compact into a single file and move the file to level 0
|
|
|
|
options = CurrentOptions();
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.target_file_size_base = INT_MAX;
|
|
|
|
options.target_file_size_multiplier = 1;
|
|
|
|
options.max_bytes_for_level_base = INT_MAX;
|
|
|
|
options.max_bytes_for_level_multiplier = 1;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = 4;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2013-09-04 22:13:08 +02:00
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.change_level = true;
|
|
|
|
compact_options.target_level = 0;
|
|
|
|
dbfull()->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
2013-09-04 22:13:08 +02:00
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
// Only 1 file in L0
|
|
|
|
ASSERT_EQ("1", FilesPerLevel(1));
|
2013-09-04 22:13:08 +02:00
|
|
|
|
|
|
|
// Stage 4: re-open in universal compaction style and do some db operations
|
|
|
|
options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = 4;
|
2013-09-04 22:13:08 +02:00
|
|
|
options.write_buffer_size = 100<<10; //100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2013-09-04 22:13:08 +02:00
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = 1;
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
|
2013-09-04 22:13:08 +02:00
|
|
|
for (int i = max_key_level_insert / 2; i <= max_key_universal_insert; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
2013-09-04 22:13:08 +02:00
|
|
|
}
|
|
|
|
dbfull()->Flush(FlushOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2013-09-04 22:13:08 +02:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
for (int i = 1; i < options.num_levels; i++) {
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(i, 1), 0);
|
2013-09-04 22:13:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// verify keys inserted in both level compaction style and universal
|
|
|
|
// compaction style
|
|
|
|
std::string keys_in_db;
|
2014-02-07 23:47:16 +01:00
|
|
|
Iterator* iter = dbfull()->NewIterator(ReadOptions(), handles_[1]);
|
2013-09-04 22:13:08 +02:00
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
keys_in_db.append(iter->key().ToString());
|
|
|
|
keys_in_db.push_back(',');
|
|
|
|
}
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
std::string expected_keys;
|
|
|
|
for (int i = 0; i <= max_key_universal_insert; i++) {
|
|
|
|
expected_keys.append(Key(i));
|
|
|
|
expected_keys.push_back(',');
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(keys_in_db, expected_keys);
|
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
TEST_F(DBTest, IncreaseUniversalCompactionNumLevels) {
|
|
|
|
std::function<void(int)> verify_func = [&](int num_keys_in_db) {
|
|
|
|
std::string keys_in_db;
|
|
|
|
Iterator* iter = dbfull()->NewIterator(ReadOptions(), handles_[1]);
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
keys_in_db.append(iter->key().ToString());
|
|
|
|
keys_in_db.push_back(',');
|
|
|
|
}
|
|
|
|
delete iter;
|
|
|
|
|
|
|
|
std::string expected_keys;
|
|
|
|
for (int i = 0; i <= num_keys_in_db; i++) {
|
|
|
|
expected_keys.append(Key(i));
|
|
|
|
expected_keys.push_back(',');
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(keys_in_db, expected_keys);
|
|
|
|
};
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int max_key1 = 200;
|
|
|
|
int max_key2 = 600;
|
|
|
|
int max_key3 = 800;
|
|
|
|
|
|
|
|
// Stage 1: open a DB with universal compaction, num_levels=1
|
2015-04-23 01:55:22 +02:00
|
|
|
Options options = CurrentOptions();
|
2015-03-30 23:04:21 +02:00
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.num_levels = 1;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
for (int i = 0; i <= max_key1; i++) {
|
|
|
|
// each value is 10K
|
|
|
|
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
int non_level0_num_files = 0;
|
|
|
|
for (int i = 1; i < options.num_levels; i++) {
|
|
|
|
non_level0_num_files += NumTableFilesAtLevel(i, 1);
|
|
|
|
}
|
|
|
|
ASSERT_EQ(non_level0_num_files, 0);
|
|
|
|
|
|
|
|
// Stage 2: reopen with universal compaction, num_levels=4
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.num_levels = 4;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
|
|
|
|
verify_func(max_key1);
|
|
|
|
|
|
|
|
// Insert more keys
|
|
|
|
for (int i = max_key1 + 1; i <= max_key2; i++) {
|
|
|
|
// each value is 10K
|
|
|
|
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
verify_func(max_key2);
|
|
|
|
// Compaction to non-L0 has happened.
|
|
|
|
ASSERT_GT(NumTableFilesAtLevel(options.num_levels - 1, 1), 0);
|
|
|
|
|
|
|
|
// Stage 3: Revert it back to one level and revert to num_levels=1.
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.target_file_size_base = INT_MAX;
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
// Compact all to level 0
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.change_level = true;
|
|
|
|
compact_options.target_level = 0;
|
|
|
|
dbfull()->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
2015-03-30 23:04:21 +02:00
|
|
|
// Need to restart it once to remove higher level records in manifest.
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
// Final reopen
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.num_levels = 1;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
|
|
|
|
|
|
// Insert more keys
|
|
|
|
for (int i = max_key2 + 1; i <= max_key3; i++) {
|
|
|
|
// each value is 10K
|
|
|
|
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
verify_func(max_key3);
|
|
|
|
}
|
|
|
|
|
2014-04-10 06:17:14 +02:00
|
|
|
namespace {
|
2012-11-01 18:50:08 +01:00
|
|
|
void MinLevelHelper(DBTest* self, Options& options) {
|
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
for (int num = 0;
|
|
|
|
num < options.level0_file_num_compaction_trigger - 1;
|
|
|
|
num++)
|
|
|
|
{
|
|
|
|
std::vector<std::string> values;
|
|
|
|
// Write 120KB (12 values, each 10K)
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
|
|
values.push_back(RandomString(&rnd, 10000));
|
|
|
|
ASSERT_OK(self->Put(Key(i), values[i]));
|
|
|
|
}
|
2013-10-15 00:12:15 +02:00
|
|
|
self->dbfull()->TEST_WaitForFlushMemTable();
|
2012-11-01 18:50:08 +01:00
|
|
|
ASSERT_EQ(self->NumTableFilesAtLevel(0), num + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
//generate one more file in level-0, and should trigger level-0 compaction
|
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
|
|
values.push_back(RandomString(&rnd, 10000));
|
|
|
|
ASSERT_OK(self->Put(Key(i), values[i]));
|
|
|
|
}
|
|
|
|
self->dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
ASSERT_EQ(self->NumTableFilesAtLevel(0), 0);
|
|
|
|
ASSERT_EQ(self->NumTableFilesAtLevel(1), 1);
|
|
|
|
}
|
|
|
|
|
2012-11-16 21:55:21 +01:00
|
|
|
// returns false if the calling-Test should be skipped
|
|
|
|
bool MinLevelToCompress(CompressionType& type, Options& options, int wbits,
|
2012-11-01 18:50:08 +01:00
|
|
|
int lev, int strategy) {
|
2012-11-05 07:04:14 +01:00
|
|
|
fprintf(stderr, "Test with compression options : window_bits = %d, level = %d, strategy = %d}\n", wbits, lev, strategy);
|
2012-11-01 18:50:08 +01:00
|
|
|
options.write_buffer_size = 100<<10; //100KB
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
2012-10-28 07:13:17 +01:00
|
|
|
options.create_if_missing = true;
|
2012-11-01 18:50:08 +01:00
|
|
|
|
2015-04-06 21:50:44 +02:00
|
|
|
if (Snappy_Supported()) {
|
2012-11-01 18:50:08 +01:00
|
|
|
type = kSnappyCompression;
|
|
|
|
fprintf(stderr, "using snappy\n");
|
2015-04-06 21:50:44 +02:00
|
|
|
} else if (Zlib_Supported()) {
|
2012-11-01 18:50:08 +01:00
|
|
|
type = kZlibCompression;
|
|
|
|
fprintf(stderr, "using zlib\n");
|
2015-04-06 21:50:44 +02:00
|
|
|
} else if (BZip2_Supported()) {
|
2012-11-01 18:50:08 +01:00
|
|
|
type = kBZip2Compression;
|
|
|
|
fprintf(stderr, "using bzip2\n");
|
2015-04-06 21:50:44 +02:00
|
|
|
} else if (LZ4_Supported()) {
|
2014-02-08 03:12:30 +01:00
|
|
|
type = kLZ4Compression;
|
|
|
|
fprintf(stderr, "using lz4\n");
|
2012-11-01 18:50:08 +01:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "skipping test, compression disabled\n");
|
2012-11-16 21:55:21 +01:00
|
|
|
return false;
|
2012-11-01 18:50:08 +01:00
|
|
|
}
|
2013-01-24 19:54:26 +01:00
|
|
|
options.compression_per_level.resize(options.num_levels);
|
2012-10-28 07:13:17 +01:00
|
|
|
|
|
|
|
// do not compress L0
|
|
|
|
for (int i = 0; i < 1; i++) {
|
|
|
|
options.compression_per_level[i] = kNoCompression;
|
|
|
|
}
|
|
|
|
for (int i = 1; i < options.num_levels; i++) {
|
|
|
|
options.compression_per_level[i] = type;
|
|
|
|
}
|
2012-11-16 21:55:21 +01:00
|
|
|
return true;
|
2012-11-01 18:50:08 +01:00
|
|
|
}
|
2014-04-10 06:17:14 +02:00
|
|
|
} // namespace
|
2013-08-08 00:20:41 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, MinLevelToCompress1) {
|
2012-11-01 18:50:08 +01:00
|
|
|
Options options = CurrentOptions();
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
CompressionType type = kSnappyCompression;
|
2012-11-16 21:55:21 +01:00
|
|
|
if (!MinLevelToCompress(type, options, -14, -1, 0)) {
|
|
|
|
return;
|
|
|
|
}
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2012-11-01 18:50:08 +01:00
|
|
|
MinLevelHelper(this, options);
|
|
|
|
|
|
|
|
// do not compress L0 and L1
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
|
|
options.compression_per_level[i] = kNoCompression;
|
|
|
|
}
|
|
|
|
for (int i = 2; i < options.num_levels; i++) {
|
|
|
|
options.compression_per_level[i] = type;
|
|
|
|
}
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2012-11-01 18:50:08 +01:00
|
|
|
MinLevelHelper(this, options);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, MinLevelToCompress2) {
|
2012-11-01 18:50:08 +01:00
|
|
|
Options options = CurrentOptions();
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
CompressionType type = kSnappyCompression;
|
2012-11-16 21:55:21 +01:00
|
|
|
if (!MinLevelToCompress(type, options, 15, -1, 0)) {
|
|
|
|
return;
|
|
|
|
}
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2012-11-01 18:50:08 +01:00
|
|
|
MinLevelHelper(this, options);
|
|
|
|
|
2012-10-28 07:13:17 +01:00
|
|
|
// do not compress L0 and L1
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
|
|
options.compression_per_level[i] = kNoCompression;
|
|
|
|
}
|
|
|
|
for (int i = 2; i < options.num_levels; i++) {
|
|
|
|
options.compression_per_level[i] = type;
|
|
|
|
}
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2012-11-01 18:50:08 +01:00
|
|
|
MinLevelHelper(this, options);
|
|
|
|
}
|
2012-10-28 07:13:17 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RepeatedWritesToSameKey) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-08-08 00:20:41 +02:00
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-07-15 02:20:57 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// We must have at most one file per level except for level-0,
|
|
|
|
// which may have up to kL0_StopWritesTrigger files.
|
2014-02-07 23:47:16 +01:00
|
|
|
const int kMaxFiles =
|
|
|
|
options.num_levels + options.level0_stop_writes_trigger;
|
2011-07-15 02:20:57 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
Random rnd(301);
|
2014-11-11 22:47:22 +01:00
|
|
|
std::string value =
|
|
|
|
RandomString(&rnd, static_cast<int>(2 * options.write_buffer_size));
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < 5 * kMaxFiles; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", value));
|
|
|
|
ASSERT_LE(TotalTableFiles(1), kMaxFiles);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
} while (ChangeCompactOptions());
|
2011-07-15 02:20:57 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, InPlaceUpdate) {
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.inplace_update_support = true;
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
|
|
|
|
// Update key with values of smaller size
|
|
|
|
int numValues = 10;
|
|
|
|
for (int i = numValues; i > 0; i--) {
|
|
|
|
std::string value = DummyString(i, 'a');
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", value));
|
|
|
|
ASSERT_EQ(value, Get(1, "key"));
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only 1 instance for that key.
|
2014-02-07 23:47:16 +01:00
|
|
|
validateNumberOfEntries(1, 1);
|
2014-01-14 16:55:16 +01:00
|
|
|
|
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, InPlaceUpdateLargeNewValue) {
|
2014-01-14 16:55:16 +01:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2014-01-14 16:55:16 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.inplace_update_support = true;
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
|
|
|
|
// Update key with values of larger size
|
2014-01-14 16:55:16 +01:00
|
|
|
int numValues = 10;
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
for (int i = 0; i < numValues; i++) {
|
|
|
|
std::string value = DummyString(i, 'a');
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", value));
|
|
|
|
ASSERT_EQ(value, Get(1, "key"));
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// All 10 updates exist in the internal iterator
|
2014-02-07 23:47:16 +01:00
|
|
|
validateNumberOfEntries(numValues, 1);
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
|
2014-01-14 16:55:16 +01:00
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, InPlaceUpdateCallbackSmallerSize) {
|
2014-01-14 16:55:16 +01:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2014-01-14 16:55:16 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.inplace_update_support = true;
|
|
|
|
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000;
|
|
|
|
options.inplace_callback =
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
rocksdb::DBTest::updateInPlaceSmallerSize;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-01-14 16:55:16 +01:00
|
|
|
|
|
|
|
// Update key with values of smaller size
|
|
|
|
int numValues = 10;
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", DummyString(numValues, 'a')));
|
|
|
|
ASSERT_EQ(DummyString(numValues, 'c'), Get(1, "key"));
|
2014-01-14 16:55:16 +01:00
|
|
|
|
|
|
|
for (int i = numValues; i > 0; i--) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", DummyString(i, 'a')));
|
|
|
|
ASSERT_EQ(DummyString(i - 1, 'b'), Get(1, "key"));
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
}
|
2014-01-14 16:55:16 +01:00
|
|
|
|
|
|
|
// Only 1 instance for that key.
|
2014-02-07 23:47:16 +01:00
|
|
|
validateNumberOfEntries(1, 1);
|
2014-01-14 16:55:16 +01:00
|
|
|
|
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, InPlaceUpdateCallbackSmallerVarintSize) {
|
2014-01-14 16:55:16 +01:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.inplace_update_support = true;
|
|
|
|
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000;
|
|
|
|
options.inplace_callback =
|
|
|
|
rocksdb::DBTest::updateInPlaceSmallerVarintSize;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
|
|
|
|
// Update key with values of smaller varint size
|
|
|
|
int numValues = 265;
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", DummyString(numValues, 'a')));
|
|
|
|
ASSERT_EQ(DummyString(numValues, 'c'), Get(1, "key"));
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
|
|
|
|
for (int i = numValues; i > 0; i--) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", DummyString(i, 'a')));
|
|
|
|
ASSERT_EQ(DummyString(1, 'b'), Get(1, "key"));
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only 1 instance for that key.
|
2014-02-07 23:47:16 +01:00
|
|
|
validateNumberOfEntries(1, 1);
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
|
2014-01-14 16:55:16 +01:00
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, InPlaceUpdateCallbackLargeNewValue) {
|
2014-01-14 16:55:16 +01:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2014-01-14 16:55:16 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.inplace_update_support = true;
|
|
|
|
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000;
|
|
|
|
options.inplace_callback =
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
rocksdb::DBTest::updateInPlaceLargerSize;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-01-14 16:55:16 +01:00
|
|
|
|
|
|
|
// Update key with values of larger size
|
|
|
|
int numValues = 10;
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
for (int i = 0; i < numValues; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", DummyString(i, 'a')));
|
|
|
|
ASSERT_EQ(DummyString(i, 'c'), Get(1, "key"));
|
2014-01-14 16:55:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// No inplace updates. All updates are puts with new seq number
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
// All 10 updates exist in the internal iterator
|
2014-02-07 23:47:16 +01:00
|
|
|
validateNumberOfEntries(numValues, 1);
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
|
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, InPlaceUpdateCallbackNoAction) {
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.inplace_update_support = true;
|
|
|
|
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000;
|
|
|
|
options.inplace_callback =
|
|
|
|
rocksdb::DBTest::updateInPlaceNoAction;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
|
Allow callback to change size of existing value. Change return type of the callback function to an enum status to handle 3 cases.
Summary:
This diff fixes 2 hacks:
* The callback function can modify the existing value inplace, if the merged value fits within the existing buffer size. But currently the existing buffer size is not being modified. Now the callback recieves a int* allowing the size to be modified. Since size is encoded as a varint in the internal key for memtable. It might happen that the entire value might have be copied to the new location if the new size varint is smaller than the existing size varint.
* The callback function has 3 functionalities
1. Modify existing buffer inplace, and update size correspondingly. Now to indicate that, Returns 1.
2. Generate a new buffer indicating merged value. Returns 2.
3. Fails to do either of above, based on whatever application logic. Returns 0.
Test Plan: Just make all for now. I'm adding another unit test to test each scenario.
Reviewers: dhruba, haobo
Reviewed By: haobo
CC: leveldb, sdong, kailiu, xinyaohu, sumeet, danguo
Differential Revision: https://reviews.facebook.net/D15195
2014-01-17 00:11:19 +01:00
|
|
|
// Callback function requests no actions from db
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "key", DummyString(1, 'a')));
|
|
|
|
ASSERT_EQ(Get(1, "key"), "NOT_FOUND");
|
In-place updates for equal keys and similar sized values
Summary:
Currently for each put, a fresh memory is allocated, and a new entry is added to the memtable with a new sequence number irrespective of whether the key already exists in the memtable. This diff is an attempt to update the value inplace for existing keys. It currently handles a very simple case:
1. Key already exists in the current memtable. Does not inplace update values in immutable memtable or snapshot
2. Latest value type is a 'put' ie kTypeValue
3. New value size is less than existing value, to avoid reallocating memory
TODO: For a put of an existing key, deallocate memory take by values, for other value types till a kTypeValue is found, ie. remove kTypeMerge.
TODO: Update the transaction log, to allow consistent reload of the memtable.
Test Plan: Added a unit test verifying the inplace update. But some other unit tests broken due to invalid sequence number checks. WIll fix them next.
Reviewers: xinyaohu, sumeet, haobo, dhruba
CC: leveldb
Differential Revision: https://reviews.facebook.net/D12423
Automatic commit by arc
2013-08-19 23:12:47 +02:00
|
|
|
|
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilter) {
|
2012-10-29 09:13:41 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-03-21 01:32:55 +01:00
|
|
|
options.max_open_files = -1;
|
2012-10-29 09:13:41 +01:00
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
2013-08-13 19:56:20 +02:00
|
|
|
options.compaction_filter_factory = std::make_shared<KeepFilterFactory>();
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-10-29 09:13:41 +01:00
|
|
|
|
2013-05-04 00:26:12 +02:00
|
|
|
// Write 100K keys, these are written to a few files in L0.
|
2012-10-29 09:13:41 +01:00
|
|
|
const std::string value(10, 'x');
|
2013-05-04 00:26:12 +02:00
|
|
|
for (int i = 0; i < 100000; i++) {
|
2012-10-29 09:13:41 +01:00
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%010d", i);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, key, value);
|
2012-10-29 09:13:41 +01:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2012-10-29 09:13:41 +01:00
|
|
|
|
|
|
|
// Push all files to the highest level L2. Verify that
|
|
|
|
// the compaction is each level invokes the filter for
|
|
|
|
// all the keys in that level.
|
|
|
|
cfilter_count = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
2012-10-29 09:13:41 +01:00
|
|
|
ASSERT_EQ(cfilter_count, 100000);
|
|
|
|
cfilter_count = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
2012-10-29 09:13:41 +01:00
|
|
|
ASSERT_EQ(cfilter_count, 100000);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
|
|
|
ASSERT_NE(NumTableFilesAtLevel(2, 1), 0);
|
2012-10-29 09:13:41 +01:00
|
|
|
cfilter_count = 0;
|
|
|
|
|
2013-02-15 23:31:24 +01:00
|
|
|
// All the files are in the lowest level.
|
|
|
|
// Verify that all but the 100001st record
|
|
|
|
// has sequence number zero. The 100001st record
|
|
|
|
// is at the tip of this snapshot and cannot
|
|
|
|
// be zeroed out.
|
2013-05-04 00:26:12 +02:00
|
|
|
// TODO: figure out sequence number squashtoo
|
2013-02-15 23:31:24 +01:00
|
|
|
int count = 0;
|
|
|
|
int total = 0;
|
2014-09-05 02:40:41 +02:00
|
|
|
Arena arena;
|
|
|
|
{
|
|
|
|
ScopedArenaIterator iter(
|
|
|
|
dbfull()->TEST_NewInternalIterator(&arena, handles_[1]));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
while (iter->Valid()) {
|
|
|
|
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
|
|
|
ikey.sequence = -1;
|
|
|
|
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
|
|
|
total++;
|
|
|
|
if (ikey.sequence != 0) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
iter->Next();
|
2013-02-15 23:31:24 +01:00
|
|
|
}
|
|
|
|
}
|
2013-05-04 00:26:12 +02:00
|
|
|
ASSERT_EQ(total, 100000);
|
2013-02-15 23:31:24 +01:00
|
|
|
ASSERT_EQ(count, 1);
|
|
|
|
|
2013-05-04 00:26:12 +02:00
|
|
|
// overwrite all the 100K keys once again.
|
|
|
|
for (int i = 0; i < 100000; i++) {
|
2012-10-29 09:13:41 +01:00
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%010d", i);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, key, value));
|
2012-10-29 09:13:41 +01:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2012-10-29 09:13:41 +01:00
|
|
|
|
|
|
|
// push all files to the highest level L2. This
|
|
|
|
// means that all keys should pass at least once
|
|
|
|
// via the compaction filter
|
|
|
|
cfilter_count = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
2012-10-29 09:13:41 +01:00
|
|
|
ASSERT_EQ(cfilter_count, 100000);
|
|
|
|
cfilter_count = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
2012-10-29 09:13:41 +01:00
|
|
|
ASSERT_EQ(cfilter_count, 100000);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
|
|
|
ASSERT_NE(NumTableFilesAtLevel(2, 1), 0);
|
2012-10-29 09:13:41 +01:00
|
|
|
|
|
|
|
// create a new database with the compaction
|
|
|
|
// filter in such a way that it deletes all keys
|
2013-08-13 19:56:20 +02:00
|
|
|
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
2012-10-29 09:13:41 +01:00
|
|
|
options.create_if_missing = true;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-10-29 09:13:41 +01:00
|
|
|
|
|
|
|
// write all the keys once again.
|
2013-05-04 00:26:12 +02:00
|
|
|
for (int i = 0; i < 100000; i++) {
|
2012-10-29 09:13:41 +01:00
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%010d", i);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, key, value));
|
2012-10-29 09:13:41 +01:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_NE(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2, 1), 0);
|
2012-10-29 09:13:41 +01:00
|
|
|
|
|
|
|
// Push all files to the highest level L2. This
|
|
|
|
// triggers the compaction filter to delete all keys,
|
|
|
|
// verify that at the end of the compaction process,
|
|
|
|
// nothing is left.
|
|
|
|
cfilter_count = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
2012-10-29 09:13:41 +01:00
|
|
|
ASSERT_EQ(cfilter_count, 100000);
|
|
|
|
cfilter_count = 0;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
2012-10-29 09:13:41 +01:00
|
|
|
ASSERT_EQ(cfilter_count, 0);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
2012-10-29 09:13:41 +01:00
|
|
|
|
2014-10-31 19:59:54 +01:00
|
|
|
{
|
|
|
|
// Scan the entire database to ensure that nothing is left
|
|
|
|
std::unique_ptr<Iterator> iter(
|
|
|
|
db_->NewIterator(ReadOptions(), handles_[1]));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
count = 0;
|
|
|
|
while (iter->Valid()) {
|
|
|
|
count++;
|
|
|
|
iter->Next();
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 0);
|
2013-02-15 23:31:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// The sequence number of the remaining record
|
2013-03-04 19:44:04 +01:00
|
|
|
// is not zeroed out even though it is at the
|
2013-02-15 23:31:24 +01:00
|
|
|
// level Lmax because this record is at the tip
|
2013-05-04 00:26:12 +02:00
|
|
|
// TODO: remove the following or design a different
|
|
|
|
// test
|
2013-02-15 23:31:24 +01:00
|
|
|
count = 0;
|
2014-09-05 02:40:41 +02:00
|
|
|
{
|
|
|
|
ScopedArenaIterator iter(
|
|
|
|
dbfull()->TEST_NewInternalIterator(&arena, handles_[1]));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
while (iter->Valid()) {
|
|
|
|
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
|
|
|
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
|
|
|
ASSERT_NE(ikey.sequence, (unsigned)0);
|
|
|
|
count++;
|
|
|
|
iter->Next();
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 0);
|
2012-10-29 09:13:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-08 22:48:39 +02:00
|
|
|
// Tests the edge case where compaction does not produce any output -- all
|
|
|
|
// entries are deleted. The compaction should create bunch of 'DeleteFile'
|
|
|
|
// entries in VersionEdit, but none of the 'AddFile's.
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilterDeletesAll) {
|
2014-05-08 22:48:39 +02:00
|
|
|
Options options;
|
|
|
|
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.create_if_missing = true;
|
2014-10-31 23:08:10 +01:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-05-08 22:48:39 +02:00
|
|
|
|
|
|
|
// put some data
|
|
|
|
for (int table = 0; table < 4; ++table) {
|
|
|
|
for (int i = 0; i < 10 + table; ++i) {
|
2014-11-25 05:44:49 +01:00
|
|
|
Put(ToString(table * 100 + i), "val");
|
2014-05-08 22:48:39 +02:00
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
// this will produce empty file (delete compaction filter)
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
2014-11-11 22:47:22 +01:00
|
|
|
ASSERT_EQ(0U, CountLiveFiles());
|
2014-05-08 22:48:39 +02:00
|
|
|
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-05-08 22:48:39 +02:00
|
|
|
|
|
|
|
Iterator* itr = db_->NewIterator(ReadOptions());
|
|
|
|
itr->SeekToFirst();
|
|
|
|
// empty db
|
|
|
|
ASSERT_TRUE(!itr->Valid());
|
|
|
|
|
|
|
|
delete itr;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilterWithValueChange) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2013-08-08 00:20:41 +02:00
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
2013-08-13 19:56:20 +02:00
|
|
|
options.compaction_filter_factory =
|
2013-12-13 01:36:38 +01:00
|
|
|
std::make_shared<ChangeFilterFactory>();
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-10-29 09:13:41 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Write 100K+1 keys, these are written to a few files
|
|
|
|
// in L0. We do this so that the current snapshot points
|
|
|
|
// to the 100001 key.The compaction filter is not invoked
|
|
|
|
// on keys that are visible via a snapshot because we
|
|
|
|
// anyways cannot delete it.
|
|
|
|
const std::string value(10, 'x');
|
|
|
|
for (int i = 0; i < 100001; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%010d", i);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, key, value);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2012-10-29 09:13:41 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// push all files to lower levels
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2015-04-23 01:55:22 +02:00
|
|
|
if (option_config_ != kUniversalCompactionMultiLevel) {
|
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
|
|
|
} else {
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2015-04-23 01:55:22 +02:00
|
|
|
}
|
2012-10-29 09:13:41 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// re-write all data again
|
|
|
|
for (int i = 0; i < 100001; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%010d", i);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, key, value);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2012-10-29 09:13:41 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// push all files to lower levels. This should
|
|
|
|
// invoke the compaction filter for all 100000 keys.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2015-04-23 01:55:22 +02:00
|
|
|
if (option_config_ != kUniversalCompactionMultiLevel) {
|
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
|
|
|
} else {
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2015-04-23 01:55:22 +02:00
|
|
|
}
|
2012-10-29 09:13:41 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// verify that all keys now have the new value that
|
|
|
|
// was set by the compaction process.
|
2014-01-10 02:52:11 +01:00
|
|
|
for (int i = 0; i < 100001; i++) {
|
2013-08-08 00:20:41 +02:00
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%010d", i);
|
2014-02-07 23:47:16 +01:00
|
|
|
std::string newvalue = Get(1, key);
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
|
|
|
}
|
|
|
|
} while (ChangeCompactOptions());
|
2012-10-29 09:13:41 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilterWithMergeOperator) {
|
2014-11-14 00:19:57 +01:00
|
|
|
std::string one, two, three, four;
|
|
|
|
PutFixed64(&one, 1);
|
|
|
|
PutFixed64(&two, 2);
|
|
|
|
PutFixed64(&three, 3);
|
|
|
|
PutFixed64(&four, 4);
|
|
|
|
|
|
|
|
Options options;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
|
|
|
// Filter out keys with value is 2.
|
|
|
|
options.compaction_filter_factory =
|
|
|
|
std::make_shared<ConditionalFilterFactory>(two);
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// In the same compaction, a value type needs to be deleted based on
|
|
|
|
// compaction filter, and there is a merge type for the key. compaction
|
|
|
|
// filter result is ignored.
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), "foo", two));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), "foo", one));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
std::string newvalue = Get("foo");
|
|
|
|
ASSERT_EQ(newvalue, three);
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-11-14 00:19:57 +01:00
|
|
|
newvalue = Get("foo");
|
|
|
|
ASSERT_EQ(newvalue, three);
|
|
|
|
|
|
|
|
// value key can be deleted based on compaction filter, leaving only
|
|
|
|
// merge keys.
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), "bar", two));
|
|
|
|
ASSERT_OK(Flush());
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-11-14 00:19:57 +01:00
|
|
|
newvalue = Get("bar");
|
|
|
|
ASSERT_EQ("NOT_FOUND", newvalue);
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), "bar", two));
|
|
|
|
ASSERT_OK(Flush());
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-11-14 00:19:57 +01:00
|
|
|
newvalue = Get("bar");
|
|
|
|
ASSERT_EQ(two, two);
|
|
|
|
|
|
|
|
// Compaction filter never applies to merge keys.
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), "foobar", one));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), "foobar", two));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
newvalue = Get("foobar");
|
|
|
|
ASSERT_EQ(newvalue, three);
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-11-14 00:19:57 +01:00
|
|
|
newvalue = Get("foobar");
|
|
|
|
ASSERT_EQ(newvalue, three);
|
|
|
|
|
|
|
|
// In the same compaction, both of value type and merge type keys need to be
|
|
|
|
// deleted based on compaction filter, and there is a merge type for the key.
|
|
|
|
// For both keys, compaction filter results are ignored.
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), "barfoo", two));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), "barfoo", two));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
newvalue = Get("barfoo");
|
|
|
|
ASSERT_EQ(newvalue, four);
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-11-14 00:19:57 +01:00
|
|
|
newvalue = Get("barfoo");
|
|
|
|
ASSERT_EQ(newvalue, four);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilterContextManual) {
|
2014-03-20 00:20:29 +01:00
|
|
|
KeepFilterFactory* filter = new KeepFilterFactory();
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.compaction_filter_factory.reset(filter);
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.level0_file_num_compaction_trigger = 8;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-03-20 00:20:29 +01:00
|
|
|
int num_keys_per_file = 400;
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
|
|
// Write several keys.
|
|
|
|
const std::string value(10, 'x');
|
|
|
|
for (int i = 0; i < num_keys_per_file; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%08d%02d", i, j);
|
|
|
|
Put(key, value);
|
|
|
|
}
|
|
|
|
dbfull()->TEST_FlushMemTable();
|
|
|
|
// Make sure next file is much smaller so automatic compaction will not
|
|
|
|
// be triggered.
|
|
|
|
num_keys_per_file /= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Force a manual compaction
|
|
|
|
cfilter_count = 0;
|
|
|
|
filter->expect_manual_compaction_.store(true);
|
|
|
|
filter->expect_full_compaction_.store(false); // Manual compaction always
|
|
|
|
// set this flag.
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-03-20 00:20:29 +01:00
|
|
|
ASSERT_EQ(cfilter_count, 700);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(0), 1);
|
2014-03-20 00:20:29 +01:00
|
|
|
|
|
|
|
// Verify total number of keys is correct after manual compaction.
|
2014-09-05 02:40:41 +02:00
|
|
|
{
|
2014-10-01 07:12:12 +02:00
|
|
|
int count = 0;
|
|
|
|
int total = 0;
|
2014-09-05 02:40:41 +02:00
|
|
|
Arena arena;
|
|
|
|
ScopedArenaIterator iter(dbfull()->TEST_NewInternalIterator(&arena));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
while (iter->Valid()) {
|
|
|
|
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
|
|
|
ikey.sequence = -1;
|
|
|
|
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
|
|
|
total++;
|
|
|
|
if (ikey.sequence != 0) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
iter->Next();
|
2014-03-20 00:20:29 +01:00
|
|
|
}
|
2014-09-05 02:40:41 +02:00
|
|
|
ASSERT_EQ(total, 700);
|
|
|
|
ASSERT_EQ(count, 1);
|
2014-03-20 00:20:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-10 02:52:11 +01:00
|
|
|
class KeepFilterV2 : public CompactionFilterV2 {
|
|
|
|
public:
|
|
|
|
virtual std::vector<bool> Filter(int level,
|
|
|
|
const SliceVector& keys,
|
|
|
|
const SliceVector& existing_values,
|
|
|
|
std::vector<std::string>* new_values,
|
|
|
|
std::vector<bool>* values_changed)
|
|
|
|
const override {
|
|
|
|
cfilter_count++;
|
|
|
|
std::vector<bool> ret;
|
|
|
|
new_values->clear();
|
|
|
|
values_changed->clear();
|
|
|
|
for (unsigned int i = 0; i < keys.size(); ++i) {
|
|
|
|
values_changed->push_back(false);
|
|
|
|
ret.push_back(false);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "KeepFilterV2";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class DeleteFilterV2 : public CompactionFilterV2 {
|
|
|
|
public:
|
|
|
|
virtual std::vector<bool> Filter(int level,
|
|
|
|
const SliceVector& keys,
|
|
|
|
const SliceVector& existing_values,
|
|
|
|
std::vector<std::string>* new_values,
|
|
|
|
std::vector<bool>* values_changed)
|
|
|
|
const override {
|
|
|
|
cfilter_count++;
|
|
|
|
new_values->clear();
|
|
|
|
values_changed->clear();
|
|
|
|
std::vector<bool> ret;
|
|
|
|
for (unsigned int i = 0; i < keys.size(); ++i) {
|
|
|
|
values_changed->push_back(false);
|
|
|
|
ret.push_back(true);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "DeleteFilterV2";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class ChangeFilterV2 : public CompactionFilterV2 {
|
|
|
|
public:
|
|
|
|
virtual std::vector<bool> Filter(int level,
|
|
|
|
const SliceVector& keys,
|
|
|
|
const SliceVector& existing_values,
|
|
|
|
std::vector<std::string>* new_values,
|
|
|
|
std::vector<bool>* values_changed)
|
|
|
|
const override {
|
|
|
|
std::vector<bool> ret;
|
|
|
|
new_values->clear();
|
|
|
|
values_changed->clear();
|
|
|
|
for (unsigned int i = 0; i < keys.size(); ++i) {
|
|
|
|
values_changed->push_back(true);
|
|
|
|
new_values->push_back(NEW_VALUE);
|
|
|
|
ret.push_back(false);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "ChangeFilterV2";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class KeepFilterFactoryV2 : public CompactionFilterFactoryV2 {
|
|
|
|
public:
|
|
|
|
explicit KeepFilterFactoryV2(const SliceTransform* prefix_extractor)
|
|
|
|
: CompactionFilterFactoryV2(prefix_extractor) { }
|
|
|
|
|
|
|
|
virtual std::unique_ptr<CompactionFilterV2>
|
|
|
|
CreateCompactionFilterV2(
|
|
|
|
const CompactionFilterContext& context) override {
|
|
|
|
return std::unique_ptr<CompactionFilterV2>(new KeepFilterV2());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "KeepFilterFactoryV2";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class DeleteFilterFactoryV2 : public CompactionFilterFactoryV2 {
|
|
|
|
public:
|
|
|
|
explicit DeleteFilterFactoryV2(const SliceTransform* prefix_extractor)
|
|
|
|
: CompactionFilterFactoryV2(prefix_extractor) { }
|
|
|
|
|
|
|
|
virtual std::unique_ptr<CompactionFilterV2>
|
|
|
|
CreateCompactionFilterV2(
|
|
|
|
const CompactionFilterContext& context) override {
|
|
|
|
return std::unique_ptr<CompactionFilterV2>(new DeleteFilterV2());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "DeleteFilterFactoryV2";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class ChangeFilterFactoryV2 : public CompactionFilterFactoryV2 {
|
|
|
|
public:
|
|
|
|
explicit ChangeFilterFactoryV2(const SliceTransform* prefix_extractor)
|
|
|
|
: CompactionFilterFactoryV2(prefix_extractor) { }
|
|
|
|
|
|
|
|
virtual std::unique_ptr<CompactionFilterV2>
|
|
|
|
CreateCompactionFilterV2(
|
|
|
|
const CompactionFilterContext& context) override {
|
|
|
|
return std::unique_ptr<CompactionFilterV2>(new ChangeFilterV2());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "ChangeFilterFactoryV2";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilterV2) {
|
2014-01-10 02:52:11 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
|
|
|
// extract prefix
|
2014-03-29 18:34:47 +01:00
|
|
|
std::unique_ptr<const SliceTransform> prefix_extractor;
|
|
|
|
prefix_extractor.reset(NewFixedPrefixTransform(8));
|
|
|
|
|
2014-01-10 02:52:11 +01:00
|
|
|
options.compaction_filter_factory_v2
|
2014-03-29 18:34:47 +01:00
|
|
|
= std::make_shared<KeepFilterFactoryV2>(prefix_extractor.get());
|
2014-01-10 02:52:11 +01:00
|
|
|
// In a testing environment, we can only flush the application
|
|
|
|
// compaction filter buffer using universal compaction
|
|
|
|
option_config_ = kUniversalCompaction;
|
|
|
|
options.compaction_style = (rocksdb::CompactionStyle)1;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-01-10 02:52:11 +01:00
|
|
|
|
|
|
|
// Write 100K keys, these are written to a few files in L0.
|
|
|
|
const std::string value(10, 'x');
|
|
|
|
for (int i = 0; i < 100000; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%08d%010d", i , i);
|
|
|
|
Put(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
dbfull()->TEST_FlushMemTable();
|
|
|
|
|
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr);
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(NumSortedRuns(0), 1);
|
2014-01-10 02:52:11 +01:00
|
|
|
|
|
|
|
// All the files are in the lowest level.
|
|
|
|
int count = 0;
|
|
|
|
int total = 0;
|
2014-09-05 02:40:41 +02:00
|
|
|
{
|
|
|
|
Arena arena;
|
|
|
|
ScopedArenaIterator iter(dbfull()->TEST_NewInternalIterator(&arena));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
while (iter->Valid()) {
|
|
|
|
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
|
|
|
ikey.sequence = -1;
|
|
|
|
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
|
|
|
total++;
|
|
|
|
if (ikey.sequence != 0) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
iter->Next();
|
2014-01-10 02:52:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(total, 100000);
|
|
|
|
// 1 snapshot only. Since we are using universal compacton,
|
|
|
|
// the sequence no is cleared for better compression
|
|
|
|
ASSERT_EQ(count, 1);
|
|
|
|
|
|
|
|
// create a new database with the compaction
|
|
|
|
// filter in such a way that it deletes all keys
|
|
|
|
options.compaction_filter_factory_v2 =
|
2014-03-29 18:34:47 +01:00
|
|
|
std::make_shared<DeleteFilterFactoryV2>(prefix_extractor.get());
|
2014-01-10 02:52:11 +01:00
|
|
|
options.create_if_missing = true;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-01-10 02:52:11 +01:00
|
|
|
|
|
|
|
// write all the keys once again.
|
|
|
|
for (int i = 0; i < 100000; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%08d%010d", i, i);
|
|
|
|
Put(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
dbfull()->TEST_FlushMemTable();
|
|
|
|
ASSERT_NE(NumTableFilesAtLevel(0), 0);
|
|
|
|
|
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
|
|
|
|
// Scan the entire database to ensure that nothing is left
|
2014-09-05 02:40:41 +02:00
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions());
|
2014-01-10 02:52:11 +01:00
|
|
|
iter->SeekToFirst();
|
|
|
|
count = 0;
|
|
|
|
while (iter->Valid()) {
|
|
|
|
count++;
|
|
|
|
iter->Next();
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(count, 0);
|
|
|
|
delete iter;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilterV2WithValueChange) {
|
2014-01-10 02:52:11 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
2014-03-29 18:34:47 +01:00
|
|
|
std::unique_ptr<const SliceTransform> prefix_extractor;
|
|
|
|
prefix_extractor.reset(NewFixedPrefixTransform(8));
|
2014-01-10 02:52:11 +01:00
|
|
|
options.compaction_filter_factory_v2 =
|
2014-03-29 18:34:47 +01:00
|
|
|
std::make_shared<ChangeFilterFactoryV2>(prefix_extractor.get());
|
2014-01-10 02:52:11 +01:00
|
|
|
// In a testing environment, we can only flush the application
|
|
|
|
// compaction filter buffer using universal compaction
|
|
|
|
option_config_ = kUniversalCompaction;
|
|
|
|
options.compaction_style = (rocksdb::CompactionStyle)1;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-01-10 02:52:11 +01:00
|
|
|
|
|
|
|
// Write 100K+1 keys, these are written to a few files
|
|
|
|
// in L0. We do this so that the current snapshot points
|
|
|
|
// to the 100001 key.The compaction filter is not invoked
|
|
|
|
// on keys that are visible via a snapshot because we
|
|
|
|
// anyways cannot delete it.
|
|
|
|
const std::string value(10, 'x');
|
|
|
|
for (int i = 0; i < 100001; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%08d%010d", i, i);
|
|
|
|
Put(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// push all files to lower levels
|
|
|
|
dbfull()->TEST_FlushMemTable();
|
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr);
|
|
|
|
|
|
|
|
// verify that all keys now have the new value that
|
|
|
|
// was set by the compaction process.
|
|
|
|
for (int i = 0; i < 100001; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%08d%010d", i, i);
|
|
|
|
std::string newvalue = Get(key);
|
|
|
|
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactionFilterV2NULLPrefix) {
|
2014-03-25 19:34:31 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.max_mem_compaction_level = 0;
|
2014-03-29 18:34:47 +01:00
|
|
|
std::unique_ptr<const SliceTransform> prefix_extractor;
|
|
|
|
prefix_extractor.reset(NewFixedPrefixTransform(8));
|
2014-03-25 19:34:31 +01:00
|
|
|
options.compaction_filter_factory_v2 =
|
2014-03-29 18:34:47 +01:00
|
|
|
std::make_shared<ChangeFilterFactoryV2>(prefix_extractor.get());
|
2014-03-25 19:34:31 +01:00
|
|
|
// In a testing environment, we can only flush the application
|
|
|
|
// compaction filter buffer using universal compaction
|
|
|
|
option_config_ = kUniversalCompaction;
|
|
|
|
options.compaction_style = (rocksdb::CompactionStyle)1;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-03-25 19:34:31 +01:00
|
|
|
|
|
|
|
// Write 100K+1 keys, these are written to a few files
|
|
|
|
// in L0. We do this so that the current snapshot points
|
|
|
|
// to the 100001 key.The compaction filter is not invoked
|
|
|
|
// on keys that are visible via a snapshot because we
|
|
|
|
// anyways cannot delete it.
|
|
|
|
const std::string value(10, 'x');
|
|
|
|
char first_key[100];
|
|
|
|
snprintf(first_key, sizeof(first_key), "%s0000%010d", "NULL", 1);
|
|
|
|
Put(first_key, value);
|
|
|
|
for (int i = 1; i < 100000; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "%08d%010d", i, i);
|
|
|
|
Put(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
char last_key[100];
|
|
|
|
snprintf(last_key, sizeof(last_key), "%s0000%010d", "NULL", 2);
|
|
|
|
Put(last_key, value);
|
|
|
|
|
|
|
|
// push all files to lower levels
|
|
|
|
dbfull()->TEST_FlushMemTable();
|
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
|
|
|
|
|
|
|
// verify that all keys now have the new value that
|
|
|
|
// was set by the compaction process.
|
|
|
|
std::string newvalue = Get(first_key);
|
|
|
|
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
|
|
|
newvalue = Get(last_key);
|
|
|
|
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
|
|
|
for (int i = 1; i < 100000; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "%08d%010d", i, i);
|
2014-10-31 19:59:54 +01:00
|
|
|
newvalue = Get(key);
|
2014-03-25 19:34:31 +01:00
|
|
|
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, SparseMerge) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compression = kNoCompression;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-03-22 19:32:49 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
FillLevels("A", "Z", 1);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Suppose there is:
|
|
|
|
// small amount of data with prefix A
|
|
|
|
// large amount of data with prefix B
|
|
|
|
// small amount of data with prefix C
|
|
|
|
// and that recent updates have made small changes to all three prefixes.
|
|
|
|
// Check that we do not do a compaction that merges all of B in one shot.
|
|
|
|
const std::string value(1000, 'x');
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "A", "va");
|
2013-08-08 00:20:41 +02:00
|
|
|
// Write approximately 100MB of "B" values
|
|
|
|
for (int i = 0; i < 100000; i++) {
|
|
|
|
char key[100];
|
|
|
|
snprintf(key, sizeof(key), "B%010d", i);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, key, value);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "C", "vc");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
2011-03-22 19:32:49 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Make sparse update
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "A", "va2");
|
|
|
|
Put(1, "B100", "bvalue2");
|
|
|
|
Put(1, "C", "vc2");
|
|
|
|
ASSERT_OK(Flush(1));
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Compactions should not cause us to create a situation where
|
|
|
|
// a file overlaps too much data at the next level.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_LE(dbfull()->TEST_MaxNextLevelOverlappingBytes(handles_[1]),
|
|
|
|
20 * 1048576);
|
2013-08-08 00:20:41 +02:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_LE(dbfull()->TEST_MaxNextLevelOverlappingBytes(handles_[1]),
|
|
|
|
20 * 1048576);
|
2013-08-08 00:20:41 +02:00
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_LE(dbfull()->TEST_MaxNextLevelOverlappingBytes(handles_[1]),
|
|
|
|
20 * 1048576);
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2011-03-22 19:32:49 +01:00
|
|
|
}
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
static bool Between(uint64_t val, uint64_t low, uint64_t high) {
|
|
|
|
bool result = (val >= low) && (val <= high);
|
|
|
|
if (!result) {
|
|
|
|
fprintf(stderr, "Value %llu is not in range [%llu, %llu]\n",
|
|
|
|
(unsigned long long)(val),
|
|
|
|
(unsigned long long)(low),
|
|
|
|
(unsigned long long)(high));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-06-13 03:04:30 +02:00
|
|
|
TEST_F(DBTest, ApproximateSizesMemTable) {
|
|
|
|
Options options;
|
|
|
|
options.write_buffer_size = 100000000; // Large write buffer
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
const int N = 128;
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t size;
|
|
|
|
std::string start = Key(50);
|
|
|
|
std::string end = Key(60);
|
|
|
|
Range r(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, true);
|
|
|
|
ASSERT_GT(size, 6000);
|
|
|
|
ASSERT_LT(size, 204800);
|
|
|
|
// Zero if not including mem table
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, false);
|
|
|
|
ASSERT_EQ(size, 0);
|
|
|
|
|
|
|
|
start = Key(500);
|
|
|
|
end = Key(600);
|
|
|
|
r = Range(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, true);
|
|
|
|
ASSERT_EQ(size, 0);
|
|
|
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
ASSERT_OK(Put(Key(1000 + i), RandomString(&rnd, 1024)));
|
|
|
|
}
|
|
|
|
|
|
|
|
start = Key(500);
|
|
|
|
end = Key(600);
|
|
|
|
r = Range(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, true);
|
|
|
|
ASSERT_EQ(size, 0);
|
|
|
|
|
|
|
|
start = Key(100);
|
|
|
|
end = Key(1020);
|
|
|
|
r = Range(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, true);
|
|
|
|
ASSERT_GT(size, 6000);
|
|
|
|
|
|
|
|
options.max_write_buffer_number = 8;
|
|
|
|
options.min_write_buffer_number_to_merge = 5;
|
|
|
|
options.write_buffer_size = 1024 * N; // Not very large
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
int keys[N * 3];
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
keys[i * 3] = i * 5;
|
|
|
|
keys[i * 3 + 1] = i * 5 + 1;
|
|
|
|
keys[i * 3 + 2] = i * 5 + 2;
|
|
|
|
}
|
|
|
|
std::random_shuffle(std::begin(keys), std::end(keys));
|
|
|
|
|
|
|
|
for (int i = 0; i < N * 3; i++) {
|
|
|
|
ASSERT_OK(Put(Key(keys[i] + 1000), RandomString(&rnd, 1024)));
|
|
|
|
}
|
|
|
|
|
|
|
|
start = Key(100);
|
|
|
|
end = Key(300);
|
|
|
|
r = Range(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, true);
|
|
|
|
ASSERT_EQ(size, 0);
|
|
|
|
|
|
|
|
start = Key(1050);
|
|
|
|
end = Key(1080);
|
|
|
|
r = Range(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, true);
|
|
|
|
ASSERT_GT(size, 6000);
|
|
|
|
|
|
|
|
start = Key(2100);
|
|
|
|
end = Key(2300);
|
|
|
|
r = Range(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size, true);
|
|
|
|
ASSERT_EQ(size, 0);
|
|
|
|
|
|
|
|
start = Key(1050);
|
|
|
|
end = Key(1080);
|
|
|
|
r = Range(start, end);
|
|
|
|
uint64_t size_with_mt, size_without_mt;
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size_with_mt, true);
|
|
|
|
ASSERT_GT(size_with_mt, 6000);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size_without_mt, false);
|
|
|
|
ASSERT_EQ(size_without_mt, 0);
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i + 1000), RandomString(&rnd, 1024)));
|
|
|
|
}
|
|
|
|
|
|
|
|
start = Key(1050);
|
|
|
|
end = Key(1080);
|
|
|
|
r = Range(start, end);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size_with_mt, true);
|
|
|
|
db_->GetApproximateSizes(&r, 1, &size_without_mt, false);
|
|
|
|
ASSERT_GT(size_with_mt, size_without_mt);
|
|
|
|
ASSERT_GT(size_without_mt, 6000);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ApproximateSizes) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
Options options;
|
2012-04-17 17:36:46 +02:00
|
|
|
options.write_buffer_size = 100000000; // Large write buffer
|
|
|
|
options.compression = kNoCompression;
|
2014-10-29 19:59:18 +01:00
|
|
|
options.create_if_missing = true;
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(Between(Size("", "xyz", 1), 0, 0));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(Between(Size("", "xyz", 1), 0, 0));
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Write 8MB (80 values, each 100K)
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
2012-04-17 17:36:46 +02:00
|
|
|
const int N = 80;
|
|
|
|
static const int S1 = 100000;
|
|
|
|
static const int S2 = 105000; // Allow some expansion from metadata
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < N; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, S1)));
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// 0 because GetApproximateSizes() does not account for memtable space
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(Between(Size("", Key(50), 1), 0, 0));
|
2011-04-20 01:01:25 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Check sizes across recovery by reopening a few times
|
|
|
|
for (int run = 0; run < 3; run++) {
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2011-04-21 00:48:11 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
for (int compact_start = 0; compact_start < N; compact_start += 10) {
|
|
|
|
for (int i = 0; i < N; i += 10) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(Between(Size("", Key(i), 1), S1 * i, S2 * i));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(i) + ".suffix", 1), S1 * (i + 1),
|
|
|
|
S2 * (i + 1)));
|
|
|
|
ASSERT_TRUE(Between(Size(Key(i), Key(i + 10), 1), S1 * 10, S2 * 10));
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(Between(Size("", Key(50), 1), S1 * 50, S2 * 50));
|
|
|
|
ASSERT_TRUE(
|
|
|
|
Between(Size("", Key(50) + ".suffix", 1), S1 * 50, S2 * 50));
|
2012-04-17 17:36:46 +02:00
|
|
|
|
|
|
|
std::string cstart_str = Key(compact_start);
|
|
|
|
std::string cend_str = Key(compact_start + 9);
|
|
|
|
Slice cstart = cstart_str;
|
|
|
|
Slice cend = cend_str;
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(0, &cstart, &cend, handles_[1]);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2011-04-21 00:48:11 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_GT(NumTableFilesAtLevel(1, 1), 0);
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
2013-12-20 18:35:24 +01:00
|
|
|
// ApproximateOffsetOf() is not yet implemented in plain table format.
|
2014-05-21 20:43:35 +02:00
|
|
|
} while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction |
|
2014-08-08 18:44:14 +02:00
|
|
|
kSkipPlainTable | kSkipHashIndex));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ApproximateSizes_MixOfSmallAndLarge) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compression = kNoCompression;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
Random rnd(301);
|
|
|
|
std::string big1 = RandomString(&rnd, 100000);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(0), RandomString(&rnd, 10000)));
|
|
|
|
ASSERT_OK(Put(1, Key(1), RandomString(&rnd, 10000)));
|
|
|
|
ASSERT_OK(Put(1, Key(2), big1));
|
|
|
|
ASSERT_OK(Put(1, Key(3), RandomString(&rnd, 10000)));
|
|
|
|
ASSERT_OK(Put(1, Key(4), big1));
|
|
|
|
ASSERT_OK(Put(1, Key(5), RandomString(&rnd, 10000)));
|
|
|
|
ASSERT_OK(Put(1, Key(6), RandomString(&rnd, 300000)));
|
|
|
|
ASSERT_OK(Put(1, Key(7), RandomString(&rnd, 10000)));
|
2012-04-17 17:36:46 +02:00
|
|
|
|
|
|
|
// Check sizes across recovery by reopening a few times
|
|
|
|
for (int run = 0; run < 3; run++) {
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(Between(Size("", Key(0), 1), 0, 0));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(1), 1), 10000, 11000));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(2), 1), 20000, 21000));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(3), 1), 120000, 121000));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(4), 1), 130000, 131000));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(5), 1), 230000, 231000));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(6), 1), 240000, 241000));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(7), 1), 540000, 541000));
|
|
|
|
ASSERT_TRUE(Between(Size("", Key(8), 1), 550000, 560000));
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(Between(Size(Key(3), Key(5), 1), 110000, 111000));
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
2013-12-20 18:35:24 +01:00
|
|
|
// ApproximateOffsetOf() is not yet implemented in plain table format.
|
|
|
|
} while (ChangeOptions(kSkipPlainTable));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, IteratorPinsRef) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "hello");
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Get iterator that will yield the current contents of the DB.
|
2014-02-07 23:47:16 +01:00
|
|
|
Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Write to force compactions
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "newvalue1");
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < 100; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
// 100K values
|
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i) + std::string(100000, 'v')));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "newvalue2");
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ("foo", iter->key().ToString());
|
|
|
|
ASSERT_EQ("hello", iter->value().ToString());
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
delete iter;
|
|
|
|
} while (ChangeCompactOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, Snapshot) {
|
2015-02-02 23:49:22 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2015-02-02 23:49:22 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override));
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(0, "foo", "0v1");
|
|
|
|
Put(1, "foo", "1v1");
|
2014-12-06 01:12:10 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
const Snapshot* s1 = db_->GetSnapshot();
|
2014-12-06 01:12:10 +01:00
|
|
|
ASSERT_EQ(1U, GetNumSnapshots());
|
|
|
|
uint64_t time_snap1 = GetTimeOldestSnapshots();
|
|
|
|
ASSERT_GT(time_snap1, 0U);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(0, "foo", "0v2");
|
|
|
|
Put(1, "foo", "1v2");
|
2014-12-06 01:12:10 +01:00
|
|
|
|
2015-05-16 00:52:51 +02:00
|
|
|
env_->addon_time_.fetch_add(1);
|
2014-12-06 01:12:10 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
const Snapshot* s2 = db_->GetSnapshot();
|
2014-12-06 01:12:10 +01:00
|
|
|
ASSERT_EQ(2U, GetNumSnapshots());
|
|
|
|
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(0, "foo", "0v3");
|
|
|
|
Put(1, "foo", "1v3");
|
2014-12-06 01:12:10 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
const Snapshot* s3 = db_->GetSnapshot();
|
2014-12-06 01:12:10 +01:00
|
|
|
ASSERT_EQ(3U, GetNumSnapshots());
|
|
|
|
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(0, "foo", "0v4");
|
|
|
|
Put(1, "foo", "1v4");
|
|
|
|
ASSERT_EQ("0v1", Get(0, "foo", s1));
|
|
|
|
ASSERT_EQ("1v1", Get(1, "foo", s1));
|
|
|
|
ASSERT_EQ("0v2", Get(0, "foo", s2));
|
|
|
|
ASSERT_EQ("1v2", Get(1, "foo", s2));
|
|
|
|
ASSERT_EQ("0v3", Get(0, "foo", s3));
|
|
|
|
ASSERT_EQ("1v3", Get(1, "foo", s3));
|
|
|
|
ASSERT_EQ("0v4", Get(0, "foo"));
|
|
|
|
ASSERT_EQ("1v4", Get(1, "foo"));
|
2012-04-17 17:36:46 +02:00
|
|
|
|
|
|
|
db_->ReleaseSnapshot(s3);
|
2014-12-06 01:12:10 +01:00
|
|
|
ASSERT_EQ(2U, GetNumSnapshots());
|
|
|
|
ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("0v1", Get(0, "foo", s1));
|
|
|
|
ASSERT_EQ("1v1", Get(1, "foo", s1));
|
|
|
|
ASSERT_EQ("0v2", Get(0, "foo", s2));
|
|
|
|
ASSERT_EQ("1v2", Get(1, "foo", s2));
|
|
|
|
ASSERT_EQ("0v4", Get(0, "foo"));
|
|
|
|
ASSERT_EQ("1v4", Get(1, "foo"));
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
db_->ReleaseSnapshot(s1);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("0v2", Get(0, "foo", s2));
|
|
|
|
ASSERT_EQ("1v2", Get(1, "foo", s2));
|
|
|
|
ASSERT_EQ("0v4", Get(0, "foo"));
|
|
|
|
ASSERT_EQ("1v4", Get(1, "foo"));
|
2014-12-06 01:12:10 +01:00
|
|
|
ASSERT_EQ(1U, GetNumSnapshots());
|
|
|
|
ASSERT_LT(time_snap1, GetTimeOldestSnapshots());
|
2011-06-22 04:36:45 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
db_->ReleaseSnapshot(s2);
|
2014-12-06 01:12:10 +01:00
|
|
|
ASSERT_EQ(0U, GetNumSnapshots());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("0v4", Get(0, "foo"));
|
|
|
|
ASSERT_EQ("1v4", Get(1, "foo"));
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
} while (ChangeOptions(kSkipHashCuckoo));
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, HiddenValuesAreRemoved) {
|
2015-02-02 23:49:22 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2015-02-02 23:49:22 +01:00
|
|
|
Options options = CurrentOptions(options_override);
|
2014-09-18 22:32:44 +02:00
|
|
|
options.max_background_flushes = 0;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-04-17 17:36:46 +02:00
|
|
|
Random rnd(301);
|
2014-02-07 23:47:16 +01:00
|
|
|
FillLevels("a", "z", 1);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
|
|
|
std::string big = RandomString(&rnd, 50000);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", big);
|
|
|
|
Put(1, "pastfoo", "v");
|
2012-04-17 17:36:46 +02:00
|
|
|
const Snapshot* snapshot = db_->GetSnapshot();
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "tiny");
|
|
|
|
Put(1, "pastfoo2", "v2"); // Advance sequence number one more
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_GT(NumTableFilesAtLevel(0, 1), 0);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(big, Get(1, "foo", snapshot));
|
|
|
|
ASSERT_TRUE(Between(Size("", "pastfoo", 1), 50000, 60000));
|
2012-04-17 17:36:46 +02:00
|
|
|
db_->ReleaseSnapshot(snapshot);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny, " + big + " ]");
|
2012-04-17 17:36:46 +02:00
|
|
|
Slice x("x");
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(0, nullptr, &x, handles_[1]);
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny ]");
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
|
|
|
ASSERT_GE(NumTableFilesAtLevel(1, 1), 1);
|
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, &x, handles_[1]);
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny ]");
|
|
|
|
|
|
|
|
ASSERT_TRUE(Between(Size("", "pastfoo", 1), 0, 1000));
|
2013-12-20 18:35:24 +01:00
|
|
|
// ApproximateOffsetOf() is not yet implemented in plain table format,
|
|
|
|
// which is used by Size().
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
// skip HashCuckooRep as it does not support snapshot
|
2014-05-21 20:43:35 +02:00
|
|
|
} while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction |
|
|
|
|
kSkipPlainTable | kSkipHashCuckoo));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactBetweenSnapshots) {
|
2015-02-02 23:49:22 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2012-11-27 06:16:21 +01:00
|
|
|
do {
|
2015-02-02 23:49:22 +01:00
|
|
|
Options options = CurrentOptions(options_override);
|
2014-04-25 21:23:07 +02:00
|
|
|
options.disable_auto_compactions = true;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-11-27 06:16:21 +01:00
|
|
|
Random rnd(301);
|
2014-02-07 23:47:16 +01:00
|
|
|
FillLevels("a", "z", 1);
|
2012-11-27 06:16:21 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "first");
|
2012-11-27 06:16:21 +01:00
|
|
|
const Snapshot* snapshot1 = db_->GetSnapshot();
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "second");
|
|
|
|
Put(1, "foo", "third");
|
|
|
|
Put(1, "foo", "fourth");
|
2012-11-27 06:16:21 +01:00
|
|
|
const Snapshot* snapshot2 = db_->GetSnapshot();
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "fifth");
|
|
|
|
Put(1, "foo", "sixth");
|
2012-11-27 06:16:21 +01:00
|
|
|
|
|
|
|
// All entries (including duplicates) exist
|
|
|
|
// before any compaction is triggered.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ("sixth", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("fourth", Get(1, "foo", snapshot2));
|
|
|
|
ASSERT_EQ("first", Get(1, "foo", snapshot1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1),
|
2012-11-27 06:16:21 +01:00
|
|
|
"[ sixth, fifth, fourth, third, second, first ]");
|
|
|
|
|
|
|
|
// After a compaction, "second", "third" and "fifth" should
|
|
|
|
// be removed
|
2014-02-07 23:47:16 +01:00
|
|
|
FillLevels("a", "z", 1);
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("sixth", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("fourth", Get(1, "foo", snapshot2));
|
|
|
|
ASSERT_EQ("first", Get(1, "foo", snapshot1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth, fourth, first ]");
|
2012-11-27 06:16:21 +01:00
|
|
|
|
|
|
|
// after we release the snapshot1, only two values left
|
|
|
|
db_->ReleaseSnapshot(snapshot1);
|
2014-02-07 23:47:16 +01:00
|
|
|
FillLevels("a", "z", 1);
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2012-11-27 06:16:21 +01:00
|
|
|
|
|
|
|
// We have only one valid snapshot snapshot2. Since snapshot1 is
|
|
|
|
// not valid anymore, "first" should be removed by a compaction.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("sixth", Get(1, "foo"));
|
|
|
|
ASSERT_EQ("fourth", Get(1, "foo", snapshot2));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth, fourth ]");
|
2012-11-27 06:16:21 +01:00
|
|
|
|
|
|
|
// after we release the snapshot2, only one value should be left
|
|
|
|
db_->ReleaseSnapshot(snapshot2);
|
2014-02-07 23:47:16 +01:00
|
|
|
FillLevels("a", "z", 1);
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("sixth", Get(1, "foo"));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth ]");
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
// skip HashCuckooRep as it does not support snapshot
|
2014-05-21 20:43:35 +02:00
|
|
|
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction));
|
2012-11-27 06:16:21 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DeletionMarkers1) {
|
2014-09-18 22:32:44 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v1");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
const int last = CurrentOptions().max_mem_compaction_level;
|
|
|
|
// foo => v1 is now in last level
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1);
|
2011-06-22 04:36:45 +02:00
|
|
|
|
|
|
|
// Place a table at level last-1 to prevent merging with preceding mutation
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "a", "begin");
|
|
|
|
Put(1, "z", "end");
|
|
|
|
Flush(1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(last - 1, 1), 1);
|
|
|
|
|
|
|
|
Delete(1, "foo");
|
|
|
|
Put(1, "foo", "v2");
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, DEL, v1 ]");
|
|
|
|
ASSERT_OK(Flush(1)); // Moves to level last-2
|
2013-02-28 23:09:30 +01:00
|
|
|
if (CurrentOptions().purge_redundant_kvs_while_flush) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, v1 ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
} else {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, DEL, v1 ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
}
|
2011-10-06 01:30:28 +02:00
|
|
|
Slice z("z");
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(last - 2, nullptr, &z, handles_[1]);
|
2011-03-18 23:37:00 +01:00
|
|
|
// DEL eliminated, but v1 remains because we aren't compacting that level
|
|
|
|
// (DEL can be eliminated because v2 hides v1).
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, v1 ]");
|
|
|
|
dbfull()->TEST_CompactRange(last - 1, nullptr, nullptr, handles_[1]);
|
2011-06-22 04:36:45 +02:00
|
|
|
// Merging last-1 w/ last, so we are the base level for "foo", so
|
|
|
|
// DEL is removed. (as is v1).
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2 ]");
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DeletionMarkers2) {
|
2014-09-18 22:32:44 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v1");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
const int last = CurrentOptions().max_mem_compaction_level;
|
|
|
|
// foo => v1 is now in last level
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1);
|
2011-06-22 04:36:45 +02:00
|
|
|
|
|
|
|
// Place a table at level last-1 to prevent merging with preceding mutation
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "a", "begin");
|
|
|
|
Put(1, "z", "end");
|
|
|
|
Flush(1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(last - 1, 1), 1);
|
|
|
|
|
|
|
|
Delete(1, "foo");
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v1 ]");
|
|
|
|
ASSERT_OK(Flush(1)); // Moves to level last-2
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v1 ]");
|
|
|
|
dbfull()->TEST_CompactRange(last - 2, nullptr, nullptr, handles_[1]);
|
2011-06-22 04:36:45 +02:00
|
|
|
// DEL kept: "last" file overlaps
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v1 ]");
|
|
|
|
dbfull()->TEST_CompactRange(last - 1, nullptr, nullptr, handles_[1]);
|
2011-06-22 04:36:45 +02:00
|
|
|
// Merging last-1 w/ last, so we are the base level for "foo", so
|
|
|
|
// DEL is removed. (as is v1).
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]");
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, OverlapInLevel0) {
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2014-09-18 22:32:44 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
int tmp = CurrentOptions().max_mem_compaction_level;
|
2012-06-23 04:30:03 +02:00
|
|
|
ASSERT_EQ(tmp, 2) << "Fix test to match config";
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2013-10-04 19:21:03 +02:00
|
|
|
//Fill levels 1 and 2 to disable the pushing of new memtables to levels > 0.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "100", "v100"));
|
|
|
|
ASSERT_OK(Put(1, "999", "v999"));
|
|
|
|
Flush(1);
|
|
|
|
ASSERT_OK(Delete(1, "100"));
|
|
|
|
ASSERT_OK(Delete(1, "999"));
|
|
|
|
Flush(1);
|
|
|
|
ASSERT_EQ("0,1,1", FilesPerLevel(1));
|
2012-04-17 17:36:46 +02:00
|
|
|
|
|
|
|
// Make files spanning the following ranges in level-0:
|
|
|
|
// files[0] 200 .. 900
|
|
|
|
// files[1] 300 .. 500
|
|
|
|
// Note that files are sorted by smallest key.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "300", "v300"));
|
|
|
|
ASSERT_OK(Put(1, "500", "v500"));
|
|
|
|
Flush(1);
|
|
|
|
ASSERT_OK(Put(1, "200", "v200"));
|
|
|
|
ASSERT_OK(Put(1, "600", "v600"));
|
|
|
|
ASSERT_OK(Put(1, "900", "v900"));
|
|
|
|
Flush(1);
|
|
|
|
ASSERT_EQ("2,1,1", FilesPerLevel(1));
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Compact away the placeholder files we created initially
|
2014-02-07 23:47:16 +01:00
|
|
|
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
|
|
|
dbfull()->TEST_CompactRange(2, nullptr, nullptr, handles_[1]);
|
|
|
|
ASSERT_EQ("2", FilesPerLevel(1));
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Do a memtable compaction. Before bug-fix, the compaction would
|
|
|
|
// not detect the overlap with level-0 files and would incorrectly place
|
|
|
|
// the deletion in a deeper level.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "600"));
|
|
|
|
Flush(1);
|
|
|
|
ASSERT_EQ("3", FilesPerLevel(1));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, "600"));
|
2014-05-21 20:43:35 +02:00
|
|
|
} while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction));
|
2011-10-06 01:30:28 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, L0_CompactionBug_Issue44_a) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "b", "v"));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "b"));
|
|
|
|
ASSERT_OK(Delete(1, "a"));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "a"));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "a", "v"));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("(a->v)", Contents(1));
|
2013-08-08 00:20:41 +02:00
|
|
|
env_->SleepForMicroseconds(1000000); // Wait for compaction to finish
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("(a->v)", Contents(1));
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2011-10-31 18:22:06 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, L0_CompactionBug_Issue44_b) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "", "");
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Delete(1, "e");
|
|
|
|
Put(1, "", "");
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "c", "cv");
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "", "");
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "", "");
|
2013-08-08 00:20:41 +02:00
|
|
|
env_->SleepForMicroseconds(1000000); // Wait for compaction to finish
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "d", "dv");
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "", "");
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
Delete(1, "d");
|
|
|
|
Delete(1, "b");
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("(->)(c->cv)", Contents(1));
|
2013-08-08 00:20:41 +02:00
|
|
|
env_->SleepForMicroseconds(1000000); // Wait for compaction to finish
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("(->)(c->cv)", Contents(1));
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2011-10-31 18:22:06 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ComparatorCheck) {
|
2011-03-18 23:37:00 +01:00
|
|
|
class NewComparator : public Comparator {
|
|
|
|
public:
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "rocksdb.NewComparator";
|
|
|
|
}
|
|
|
|
virtual int Compare(const Slice& a, const Slice& b) const override {
|
2011-03-18 23:37:00 +01:00
|
|
|
return BytewiseComparator()->Compare(a, b);
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void FindShortestSeparator(std::string* s,
|
|
|
|
const Slice& l) const override {
|
2011-03-18 23:37:00 +01:00
|
|
|
BytewiseComparator()->FindShortestSeparator(s, l);
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void FindShortSuccessor(std::string* key) const override {
|
2011-03-18 23:37:00 +01:00
|
|
|
BytewiseComparator()->FindShortSuccessor(key);
|
|
|
|
}
|
|
|
|
};
|
2014-02-07 23:47:16 +01:00
|
|
|
Options new_options, options;
|
2013-10-01 23:46:52 +02:00
|
|
|
NewComparator cmp;
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-02-07 23:47:16 +01:00
|
|
|
options = CurrentOptions();
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-10-01 23:46:52 +02:00
|
|
|
new_options = CurrentOptions();
|
2013-08-08 00:20:41 +02:00
|
|
|
new_options.comparator = &cmp;
|
2014-02-07 23:47:16 +01:00
|
|
|
// only the non-default column family has non-matching comparator
|
|
|
|
Status s = TryReopenWithColumnFamilies({"default", "pikachu"},
|
2014-10-29 20:00:42 +01:00
|
|
|
std::vector<Options>({options, new_options}));
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_TRUE(!s.ok());
|
|
|
|
ASSERT_TRUE(s.ToString().find("comparator") != std::string::npos)
|
|
|
|
<< s.ToString();
|
2014-10-29 21:36:18 +01:00
|
|
|
} while (ChangeCompactOptions());
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CustomComparator) {
|
2011-10-31 18:22:06 +01:00
|
|
|
class NumberComparator : public Comparator {
|
|
|
|
public:
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual const char* Name() const override {
|
|
|
|
return "test.NumberComparator";
|
|
|
|
}
|
|
|
|
virtual int Compare(const Slice& a, const Slice& b) const override {
|
2011-11-14 18:06:16 +01:00
|
|
|
return ToNumber(a) - ToNumber(b);
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void FindShortestSeparator(std::string* s,
|
|
|
|
const Slice& l) const override {
|
2011-11-14 18:06:16 +01:00
|
|
|
ToNumber(*s); // Check format
|
|
|
|
ToNumber(l); // Check format
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void FindShortSuccessor(std::string* key) const override {
|
2011-11-14 18:06:16 +01:00
|
|
|
ToNumber(*key); // Check format
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
static int ToNumber(const Slice& x) {
|
|
|
|
// Check that there are no extra characters.
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_TRUE(x.size() >= 2 && x[0] == '[' && x[x.size() - 1] == ']')
|
2011-11-14 18:06:16 +01:00
|
|
|
<< EscapeString(x);
|
|
|
|
int val;
|
|
|
|
char ignored;
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_TRUE(sscanf(x.ToString().c_str(), "[%i]%c", &val, &ignored) == 1)
|
2011-11-14 18:06:16 +01:00
|
|
|
<< EscapeString(x);
|
|
|
|
return val;
|
2011-10-31 18:22:06 +01:00
|
|
|
}
|
|
|
|
};
|
2013-10-01 23:46:52 +02:00
|
|
|
Options new_options;
|
|
|
|
NumberComparator cmp;
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2013-10-01 23:46:52 +02:00
|
|
|
new_options = CurrentOptions();
|
2013-08-08 00:20:41 +02:00
|
|
|
new_options.create_if_missing = true;
|
|
|
|
new_options.comparator = &cmp;
|
|
|
|
new_options.write_buffer_size = 1000; // Compact more often
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
new_options = CurrentOptions(new_options);
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(new_options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, new_options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "[10]", "ten"));
|
|
|
|
ASSERT_OK(Put(1, "[0x14]", "twenty"));
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < 2; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("ten", Get(1, "[10]"));
|
|
|
|
ASSERT_EQ("ten", Get(1, "[0xa]"));
|
|
|
|
ASSERT_EQ("twenty", Get(1, "[20]"));
|
|
|
|
ASSERT_EQ("twenty", Get(1, "[0x14]"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, "[15]"));
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, "[0xf]"));
|
|
|
|
Compact(1, "[0]", "[9999]");
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (int run = 0; run < 2; run++) {
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
|
|
char buf[100];
|
|
|
|
snprintf(buf, sizeof(buf), "[%d]", i*10);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, buf, buf));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
Compact(1, "[0]", "[1000000]");
|
2011-11-14 18:06:16 +01:00
|
|
|
}
|
2014-10-29 21:36:18 +01:00
|
|
|
} while (ChangeCompactOptions());
|
2011-10-31 18:22:06 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManualCompaction) {
|
2014-09-18 22:32:44 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-06-23 04:30:03 +02:00
|
|
|
ASSERT_EQ(dbfull()->MaxMemCompactionLevel(), 2)
|
2011-10-06 01:30:28 +02:00
|
|
|
<< "Need to update this test to match kMaxMemCompactLevel";
|
|
|
|
|
2014-01-15 01:19:09 +01:00
|
|
|
// iter - 0 with 7 levels
|
|
|
|
// iter - 1 with 3 levels
|
|
|
|
for (int iter = 0; iter < 2; ++iter) {
|
2014-02-07 23:47:16 +01:00
|
|
|
MakeTables(3, "p", "q", 1);
|
|
|
|
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
2014-01-15 01:19:09 +01:00
|
|
|
|
|
|
|
// Compaction range falls before files
|
2014-02-07 23:47:16 +01:00
|
|
|
Compact(1, "", "c");
|
|
|
|
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2014-01-15 01:19:09 +01:00
|
|
|
// Compaction range falls after files
|
2014-02-07 23:47:16 +01:00
|
|
|
Compact(1, "r", "z");
|
|
|
|
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2014-01-15 01:19:09 +01:00
|
|
|
// Compaction range overlaps files
|
2014-02-07 23:47:16 +01:00
|
|
|
Compact(1, "p1", "p9");
|
|
|
|
ASSERT_EQ("0,0,1", FilesPerLevel(1));
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2014-01-15 01:19:09 +01:00
|
|
|
// Populate a different range
|
2014-02-07 23:47:16 +01:00
|
|
|
MakeTables(3, "c", "e", 1);
|
|
|
|
ASSERT_EQ("1,1,2", FilesPerLevel(1));
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2014-01-15 01:19:09 +01:00
|
|
|
// Compact just the new range
|
2014-02-07 23:47:16 +01:00
|
|
|
Compact(1, "b", "f");
|
|
|
|
ASSERT_EQ("0,0,2", FilesPerLevel(1));
|
2011-10-06 01:30:28 +02:00
|
|
|
|
2014-01-15 01:19:09 +01:00
|
|
|
// Compact all
|
2014-02-07 23:47:16 +01:00
|
|
|
MakeTables(1, "a", "z", 1);
|
|
|
|
ASSERT_EQ("0,1,2", FilesPerLevel(1));
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("0,0,1", FilesPerLevel(1));
|
2014-01-15 01:19:09 +01:00
|
|
|
|
|
|
|
if (iter == 0) {
|
2014-10-31 19:59:54 +01:00
|
|
|
options = CurrentOptions();
|
2014-09-18 22:32:44 +02:00
|
|
|
options.max_background_flushes = 0;
|
2014-01-15 01:19:09 +01:00
|
|
|
options.num_levels = 3;
|
|
|
|
options.create_if_missing = true;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-01-15 01:19:09 +01:00
|
|
|
}
|
|
|
|
}
|
2011-10-06 01:30:28 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
class DBTestUniversalManualCompactionOutputPathId
|
|
|
|
: public DBTestUniversalCompactionBase {};
|
|
|
|
|
|
|
|
TEST_P(DBTestUniversalManualCompactionOutputPathId,
|
|
|
|
ManualCompactionOutputPathId) {
|
2014-07-17 02:39:18 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_paths.emplace_back(dbname_, 1000000000);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 1000000000);
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = num_levels_;
|
|
|
|
options.target_file_size_base = 1 << 30; // Big size
|
2014-07-17 02:39:18 +02:00
|
|
|
options.level0_file_num_compaction_trigger = 10;
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-07-17 02:39:18 +02:00
|
|
|
MakeTables(3, "p", "q", 1);
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(3, TotalLiveFiles(1));
|
2014-07-17 02:39:18 +02:00
|
|
|
ASSERT_EQ(3, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
// Full compaction to DB path 0
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.target_path_id = 1;
|
|
|
|
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(1, TotalLiveFiles(1));
|
2014-07-17 02:39:18 +02:00
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(1, TotalLiveFiles(1));
|
2014-07-17 02:39:18 +02:00
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
MakeTables(1, "p", "q", 1);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(2, TotalLiveFiles(1));
|
2014-07-17 02:39:18 +02:00
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(2, TotalLiveFiles(1));
|
2014-07-17 02:39:18 +02:00
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
// Full compaction to DB path 0
|
2015-06-17 23:36:14 +02:00
|
|
|
compact_options.target_path_id = 0;
|
|
|
|
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_EQ(1, TotalLiveFiles(1));
|
2014-07-17 02:39:18 +02:00
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
|
|
|
|
// Fail when compacting to an invalid path ID
|
2015-06-17 23:36:14 +02:00
|
|
|
compact_options.target_path_id = 2;
|
|
|
|
ASSERT_TRUE(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)
|
2014-07-17 02:39:18 +02:00
|
|
|
.IsInvalidArgument());
|
|
|
|
}
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
INSTANTIATE_TEST_CASE_P(DBTestUniversalManualCompactionOutputPathId,
|
|
|
|
DBTestUniversalManualCompactionOutputPathId,
|
|
|
|
::testing::Values(1, 8));
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManualLevelCompactionOutputPathId) {
|
2014-12-16 06:48:16 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 2 * 10485760);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_3", 100 * 10485760);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_4", 120 * 10485760);
|
|
|
|
options.max_background_flushes = 1;
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
ASSERT_EQ(dbfull()->MaxMemCompactionLevel(), 2)
|
|
|
|
<< "Need to update this test to match kMaxMemCompactLevel";
|
|
|
|
|
|
|
|
// iter - 0 with 7 levels
|
|
|
|
// iter - 1 with 3 levels
|
|
|
|
for (int iter = 0; iter < 2; ++iter) {
|
|
|
|
MakeTables(3, "p", "q", 1);
|
|
|
|
ASSERT_EQ("3", FilesPerLevel(1));
|
|
|
|
ASSERT_EQ(3, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// Compaction range falls before files
|
|
|
|
Compact(1, "", "c");
|
|
|
|
ASSERT_EQ("3", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compaction range falls after files
|
|
|
|
Compact(1, "r", "z");
|
|
|
|
ASSERT_EQ("3", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compaction range overlaps files
|
|
|
|
Compact(1, "p1", "p9", 1);
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(1));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// Populate a different range
|
|
|
|
MakeTables(3, "c", "e", 1);
|
|
|
|
ASSERT_EQ("3,1", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compact just the new range
|
|
|
|
Compact(1, "b", "f", 1);
|
|
|
|
ASSERT_EQ("0,2", FilesPerLevel(1));
|
|
|
|
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// Compact all
|
|
|
|
MakeTables(1, "a", "z", 1);
|
|
|
|
ASSERT_EQ("1,2", FilesPerLevel(1));
|
|
|
|
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.target_path_id = 1;
|
|
|
|
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
2014-12-16 06:48:16 +01:00
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(1));
|
|
|
|
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
if (iter == 0) {
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
options = CurrentOptions();
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_2", 2 * 10485760);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_3", 100 * 10485760);
|
|
|
|
options.db_paths.emplace_back(dbname_ + "_4", 120 * 10485760);
|
|
|
|
options.max_background_flushes = 1;
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DBOpen_Options) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
std::string dbname = test::TmpDir(env_) + "/db_options_test";
|
|
|
|
ASSERT_OK(DestroyDB(dbname, options));
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
// Does not exist, and create_if_missing == false: error
|
2013-03-01 03:04:58 +01:00
|
|
|
DB* db = nullptr;
|
2014-10-31 23:08:10 +01:00
|
|
|
options.create_if_missing = false;
|
|
|
|
Status s = DB::Open(options, dbname, &db);
|
2013-03-01 03:04:58 +01:00
|
|
|
ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr);
|
|
|
|
ASSERT_TRUE(db == nullptr);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
// Does not exist, and create_if_missing == true: OK
|
2014-10-31 23:08:10 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
s = DB::Open(options, dbname, &db);
|
2011-03-18 23:37:00 +01:00
|
|
|
ASSERT_OK(s);
|
2013-03-01 03:04:58 +01:00
|
|
|
ASSERT_TRUE(db != nullptr);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
delete db;
|
2013-03-01 03:04:58 +01:00
|
|
|
db = nullptr;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
// Does exist, and error_if_exists == true: error
|
2014-10-31 23:08:10 +01:00
|
|
|
options.create_if_missing = false;
|
|
|
|
options.error_if_exists = true;
|
|
|
|
s = DB::Open(options, dbname, &db);
|
2013-03-01 03:04:58 +01:00
|
|
|
ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr);
|
|
|
|
ASSERT_TRUE(db == nullptr);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
// Does exist, and error_if_exists == false: OK
|
2014-10-31 23:08:10 +01:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.error_if_exists = false;
|
|
|
|
s = DB::Open(options, dbname, &db);
|
2011-03-18 23:37:00 +01:00
|
|
|
ASSERT_OK(s);
|
2013-03-01 03:04:58 +01:00
|
|
|
ASSERT_TRUE(db != nullptr);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
delete db;
|
2013-03-01 03:04:58 +01:00
|
|
|
db = nullptr;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DBOpen_Change_NumLevels) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
DestroyAndReopen(options);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(db_ != nullptr);
|
2014-10-31 23:08:10 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
|
|
|
|
ASSERT_OK(Put(1, "a", "123"));
|
|
|
|
ASSERT_OK(Put(1, "b", "234"));
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
Close();
|
2012-10-29 23:25:01 +01:00
|
|
|
|
2014-10-31 23:08:10 +01:00
|
|
|
options.create_if_missing = false;
|
|
|
|
options.num_levels = 2;
|
|
|
|
Status s = TryReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-01-15 00:54:11 +01:00
|
|
|
ASSERT_TRUE(strstr(s.ToString().c_str(), "Invalid argument") != nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_TRUE(db_ == nullptr);
|
2012-10-29 23:25:01 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DestroyDBMetaDatabase) {
|
2014-10-31 23:08:10 +01:00
|
|
|
std::string dbname = test::TmpDir(env_) + "/db_meta";
|
2015-02-12 01:11:40 +01:00
|
|
|
ASSERT_OK(env_->CreateDirIfMissing(dbname));
|
2012-12-17 20:26:59 +01:00
|
|
|
std::string metadbname = MetaDatabaseName(dbname, 0);
|
2015-02-12 01:11:40 +01:00
|
|
|
ASSERT_OK(env_->CreateDirIfMissing(metadbname));
|
2012-12-17 20:26:59 +01:00
|
|
|
std::string metametadbname = MetaDatabaseName(metadbname, 0);
|
2015-02-12 01:11:40 +01:00
|
|
|
ASSERT_OK(env_->CreateDirIfMissing(metametadbname));
|
2012-12-17 20:26:59 +01:00
|
|
|
|
|
|
|
// Destroy previous versions if they exist. Using the long way.
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
ASSERT_OK(DestroyDB(metametadbname, options));
|
|
|
|
ASSERT_OK(DestroyDB(metadbname, options));
|
|
|
|
ASSERT_OK(DestroyDB(dbname, options));
|
2012-12-17 20:26:59 +01:00
|
|
|
|
|
|
|
// Setup databases
|
2013-03-01 03:04:58 +01:00
|
|
|
DB* db = nullptr;
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(DB::Open(options, dbname, &db));
|
2012-12-17 20:26:59 +01:00
|
|
|
delete db;
|
2013-03-01 03:04:58 +01:00
|
|
|
db = nullptr;
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(DB::Open(options, metadbname, &db));
|
2012-12-17 20:26:59 +01:00
|
|
|
delete db;
|
2013-03-01 03:04:58 +01:00
|
|
|
db = nullptr;
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(DB::Open(options, metametadbname, &db));
|
2012-12-17 20:26:59 +01:00
|
|
|
delete db;
|
2013-03-01 03:04:58 +01:00
|
|
|
db = nullptr;
|
2012-12-17 20:26:59 +01:00
|
|
|
|
|
|
|
// Delete databases
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(DestroyDB(dbname, options));
|
2012-12-17 20:26:59 +01:00
|
|
|
|
|
|
|
// Check if deletion worked.
|
2014-10-31 23:08:10 +01:00
|
|
|
options.create_if_missing = false;
|
|
|
|
ASSERT_TRUE(!(DB::Open(options, dbname, &db)).ok());
|
|
|
|
ASSERT_TRUE(!(DB::Open(options, metadbname, &db)).ok());
|
|
|
|
ASSERT_TRUE(!(DB::Open(options, metametadbname, &db)).ok());
|
2012-12-17 20:26:59 +01:00
|
|
|
}
|
|
|
|
|
2014-09-11 02:00:00 +02:00
|
|
|
// Check that number of files does not grow when writes are dropped
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DropWrites) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
2014-03-31 21:44:54 +02:00
|
|
|
options.paranoid_checks = false;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2012-01-25 23:56:52 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_OK(Put("foo", "v1"));
|
|
|
|
ASSERT_EQ("v1", Get("foo"));
|
|
|
|
Compact("a", "z");
|
2014-11-11 22:47:22 +01:00
|
|
|
const size_t num_files = CountFiles();
|
2014-10-27 22:50:21 +01:00
|
|
|
// Force out-of-space errors
|
|
|
|
env_->drop_writes_.store(true, std::memory_order_release);
|
2013-08-08 00:20:41 +02:00
|
|
|
env_->sleep_counter_.Reset();
|
|
|
|
for (int i = 0; i < 5; i++) {
|
2015-04-23 01:55:22 +02:00
|
|
|
if (option_config_ != kUniversalCompactionMultiLevel) {
|
|
|
|
for (int level = 0; level < dbfull()->NumberLevels(); level++) {
|
|
|
|
if (level > 0 && level == dbfull()->NumberLevels() - 1) {
|
|
|
|
break;
|
|
|
|
}
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
dbfull()->TEST_CompactRange(level, nullptr, nullptr, nullptr,
|
|
|
|
true /* disallow trivial move */);
|
2015-03-30 23:04:21 +02:00
|
|
|
}
|
2015-04-23 01:55:22 +02:00
|
|
|
} else {
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2012-01-25 23:56:52 +01:00
|
|
|
}
|
2014-03-18 20:25:08 +01:00
|
|
|
|
|
|
|
std::string property_value;
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
|
|
|
ASSERT_EQ("5", property_value);
|
|
|
|
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->drop_writes_.store(false, std::memory_order_release);
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_LT(CountFiles(), num_files + 3);
|
2012-08-23 01:57:51 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Check that compaction attempts slept after errors
|
|
|
|
ASSERT_GE(env_->sleep_counter_.Read(), 5);
|
|
|
|
} while (ChangeCompactOptions());
|
2012-08-23 01:57:51 +02:00
|
|
|
}
|
|
|
|
|
2014-03-18 20:25:08 +01:00
|
|
|
// Check background error counter bumped on flush failures.
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DropWritesFlush) {
|
2014-03-18 20:25:08 +01:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.max_background_flushes = 1;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-03-18 20:25:08 +01:00
|
|
|
|
|
|
|
ASSERT_OK(Put("foo", "v1"));
|
2014-10-27 22:50:21 +01:00
|
|
|
// Force out-of-space errors
|
|
|
|
env_->drop_writes_.store(true, std::memory_order_release);
|
2014-03-18 20:25:08 +01:00
|
|
|
|
|
|
|
std::string property_value;
|
|
|
|
// Background error count is 0 now.
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
|
|
|
ASSERT_EQ("0", property_value);
|
|
|
|
|
2014-11-07 01:07:07 +01:00
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
2014-03-18 20:25:08 +01:00
|
|
|
ASSERT_EQ("1", property_value);
|
|
|
|
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->drop_writes_.store(false, std::memory_order_release);
|
2014-09-11 02:00:00 +02:00
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that CompactRange() returns failure if there is not enough space left
|
|
|
|
// on device
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, NoSpaceCompactRange) {
|
2014-09-11 02:00:00 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.disable_auto_compactions = true;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-09-11 02:00:00 +02:00
|
|
|
|
|
|
|
// generate 5 tables
|
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
|
|
ASSERT_OK(Put(Key(i), Key(i) + "v"));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
|
2014-10-27 22:50:21 +01:00
|
|
|
// Force out-of-space errors
|
|
|
|
env_->no_space_.store(true, std::memory_order_release);
|
2014-09-11 02:00:00 +02:00
|
|
|
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
Status s = dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
|
|
|
true /* disallow trivial move */);
|
2014-09-11 02:00:00 +02:00
|
|
|
ASSERT_TRUE(s.IsIOError());
|
|
|
|
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->no_space_.store(false, std::memory_order_release);
|
2014-03-18 20:25:08 +01:00
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, NonWritableFileSystem) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.write_buffer_size = 1000;
|
|
|
|
options.env = env_;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_OK(Put("foo", "v1"));
|
2014-10-28 22:27:26 +01:00
|
|
|
env_->non_writeable_rate_.store(100);
|
2013-08-08 00:20:41 +02:00
|
|
|
std::string big(100000, 'x');
|
|
|
|
int errors = 0;
|
|
|
|
for (int i = 0; i < 20; i++) {
|
|
|
|
if (!Put("foo", big).ok()) {
|
|
|
|
errors++;
|
|
|
|
env_->SleepForMicroseconds(100000);
|
|
|
|
}
|
2012-08-23 01:57:51 +02:00
|
|
|
}
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_GT(errors, 0);
|
2014-10-28 22:27:26 +01:00
|
|
|
env_->non_writeable_rate_.store(0);
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2012-01-25 23:56:52 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManifestWriteError) {
|
2013-01-08 21:00:13 +01:00
|
|
|
// Test for the following problem:
|
|
|
|
// (a) Compaction produces file F
|
|
|
|
// (b) Log record containing F is written to MANIFEST file, but Sync() fails
|
|
|
|
// (c) GC deletes F
|
|
|
|
// (d) After reopening DB, reads fail since deleted F is named in log record
|
|
|
|
|
|
|
|
// We iterate twice. In the second iteration, everything is the
|
|
|
|
// same except the log record never makes it to the MANIFEST file.
|
|
|
|
for (int iter = 0; iter < 2; iter++) {
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool>* error_type = (iter == 0)
|
2013-01-08 21:00:13 +01:00
|
|
|
? &env_->manifest_sync_error_
|
|
|
|
: &env_->manifest_write_error_;
|
|
|
|
|
|
|
|
// Insert foo=>bar mapping
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.error_if_exists = false;
|
2014-09-18 22:32:44 +02:00
|
|
|
options.max_background_flushes = 0;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2013-01-08 21:00:13 +01:00
|
|
|
ASSERT_OK(Put("foo", "bar"));
|
|
|
|
ASSERT_EQ("bar", Get("foo"));
|
|
|
|
|
|
|
|
// Memtable compaction (will succeed)
|
2014-02-07 23:47:16 +01:00
|
|
|
Flush();
|
2013-01-08 21:00:13 +01:00
|
|
|
ASSERT_EQ("bar", Get("foo"));
|
|
|
|
const int last = dbfull()->MaxMemCompactionLevel();
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(last), 1); // foo=>bar is now in last level
|
|
|
|
|
|
|
|
// Merging compaction (will fail)
|
2014-10-27 22:50:21 +01:00
|
|
|
error_type->store(true, std::memory_order_release);
|
2013-03-01 03:04:58 +01:00
|
|
|
dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
|
2013-01-08 21:00:13 +01:00
|
|
|
ASSERT_EQ("bar", Get("foo"));
|
|
|
|
|
|
|
|
// Recovery: should not lose data
|
2014-10-27 22:50:21 +01:00
|
|
|
error_type->store(false, std::memory_order_release);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2013-01-08 21:00:13 +01:00
|
|
|
ASSERT_EQ("bar", Get("foo"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PutFailsParanoid) {
|
2013-10-28 20:36:02 +01:00
|
|
|
// Test the following:
|
|
|
|
// (a) A random put fails in paranoid mode (simulate by sync fail)
|
|
|
|
// (b) All other puts have to fail, even if writes would succeed
|
|
|
|
// (c) All of that should happen ONLY if paranoid_checks = true
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.error_if_exists = false;
|
|
|
|
options.paranoid_checks = true;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-10-28 20:36:02 +01:00
|
|
|
Status s;
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "bar"));
|
|
|
|
ASSERT_OK(Put(1, "foo1", "bar1"));
|
2013-10-28 20:36:02 +01:00
|
|
|
// simulate error
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->log_write_error_.store(true, std::memory_order_release);
|
2014-02-07 23:47:16 +01:00
|
|
|
s = Put(1, "foo2", "bar2");
|
2013-10-28 20:36:02 +01:00
|
|
|
ASSERT_TRUE(!s.ok());
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->log_write_error_.store(false, std::memory_order_release);
|
2014-02-07 23:47:16 +01:00
|
|
|
s = Put(1, "foo3", "bar3");
|
2013-10-28 20:36:02 +01:00
|
|
|
// the next put should fail, too
|
|
|
|
ASSERT_TRUE(!s.ok());
|
|
|
|
// but we're still able to read
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("bar", Get(1, "foo"));
|
2013-10-28 20:36:02 +01:00
|
|
|
|
|
|
|
// do the same thing with paranoid checks off
|
|
|
|
options.paranoid_checks = false;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-10-28 20:36:02 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "bar"));
|
|
|
|
ASSERT_OK(Put(1, "foo1", "bar1"));
|
2013-10-28 20:36:02 +01:00
|
|
|
// simulate error
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->log_write_error_.store(true, std::memory_order_release);
|
2014-02-07 23:47:16 +01:00
|
|
|
s = Put(1, "foo2", "bar2");
|
2013-10-28 20:36:02 +01:00
|
|
|
ASSERT_TRUE(!s.ok());
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->log_write_error_.store(false, std::memory_order_release);
|
2014-02-07 23:47:16 +01:00
|
|
|
s = Put(1, "foo3", "bar3");
|
2013-10-28 20:36:02 +01:00
|
|
|
// the next put should NOT fail
|
|
|
|
ASSERT_TRUE(s.ok());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FilesDeletedAfterCompaction) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v2"));
|
|
|
|
Compact(1, "a", "z");
|
2014-11-11 22:47:22 +01:00
|
|
|
const size_t num_files = CountLiveFiles();
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < 10; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "foo", "v2"));
|
|
|
|
Compact(1, "a", "z");
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
ASSERT_EQ(CountLiveFiles(), num_files);
|
|
|
|
} while (ChangeCompactOptions());
|
2012-01-25 23:56:52 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, BloomFilter) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
2014-08-25 23:22:05 +02:00
|
|
|
env_->count_random_reads_ = true;
|
2013-08-08 00:20:41 +02:00
|
|
|
options.env = env_;
|
2014-08-25 23:22:05 +02:00
|
|
|
// ChangeCompactOptions() only changes compaction style, which does not
|
|
|
|
// trigger reset of table_factory
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.no_block_cache = true;
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Populate multiple layers
|
|
|
|
const int N = 10000;
|
|
|
|
for (int i = 0; i < N; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i)));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
Compact(1, "a", "z");
|
2013-08-08 00:20:41 +02:00
|
|
|
for (int i = 0; i < N; i += 100) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i)));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
Flush(1);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Prevent auto compactions triggered by seeks
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->delay_sstable_sync_.store(true, std::memory_order_release);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Lookup present keys. Should rarely read from small sstable.
|
|
|
|
env_->random_read_counter_.Reset();
|
|
|
|
for (int i = 0; i < N; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(Key(i), Get(1, Key(i)));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
int reads = env_->random_read_counter_.Read();
|
|
|
|
fprintf(stderr, "%d present => %d reads\n", N, reads);
|
|
|
|
ASSERT_GE(reads, N);
|
|
|
|
ASSERT_LE(reads, N + 2*N/100);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Lookup present keys. Should rarely read from either sstable.
|
|
|
|
env_->random_read_counter_.Reset();
|
|
|
|
for (int i = 0; i < N; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, Key(i) + ".missing"));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
reads = env_->random_read_counter_.Read();
|
|
|
|
fprintf(stderr, "%d missing => %d reads\n", N, reads);
|
|
|
|
ASSERT_LE(reads, 3*N/100);
|
2012-04-17 17:36:46 +02:00
|
|
|
|
2014-10-27 22:50:21 +01:00
|
|
|
env_->delay_sstable_sync_.store(false, std::memory_order_release);
|
2013-08-08 00:20:41 +02:00
|
|
|
Close();
|
|
|
|
} while (ChangeCompactOptions());
|
2012-04-17 17:36:46 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, BloomFilterRate) {
|
2014-09-08 19:37:05 +02:00
|
|
|
while (ChangeFilterOptions()) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-09-08 19:37:05 +02:00
|
|
|
|
|
|
|
const int maxKey = 10000;
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i)));
|
|
|
|
}
|
|
|
|
// Add a large key to make the file contain wide range
|
|
|
|
ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
|
|
|
|
Flush(1);
|
|
|
|
|
|
|
|
// Check if they can be found
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_EQ(Key(i), Get(1, Key(i)));
|
|
|
|
}
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
|
|
|
|
|
|
|
// Check if filter is useful
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, Key(i+33333)));
|
|
|
|
}
|
|
|
|
ASSERT_GE(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), maxKey*0.98);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, BloomFilterCompatibility) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-09-08 19:37:05 +02:00
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
|
|
|
|
// Create with block based filter
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-09-08 19:37:05 +02:00
|
|
|
|
|
|
|
const int maxKey = 10000;
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
|
|
|
|
Flush(1);
|
|
|
|
|
|
|
|
// Check db with full filter
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-09-08 19:37:05 +02:00
|
|
|
|
|
|
|
// Check if they can be found
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_EQ(Key(i), Get(1, Key(i)));
|
|
|
|
}
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, BloomFilterReverseCompatibility) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-09-08 19:37:05 +02:00
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
|
|
|
|
// Create with full filter
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-09-08 19:37:05 +02:00
|
|
|
|
|
|
|
const int maxKey = 10000;
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
|
|
|
|
Flush(1);
|
|
|
|
|
|
|
|
// Check db with block_based filter
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-09-08 19:37:05 +02:00
|
|
|
|
|
|
|
// Check if they can be found
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_EQ(Key(i), Get(1, Key(i)));
|
|
|
|
}
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
|
|
|
}
|
|
|
|
|
2014-09-12 01:33:46 +02:00
|
|
|
namespace {
|
|
|
|
// A wrapped bloom over default FilterPolicy
|
|
|
|
class WrappedBloom : public FilterPolicy {
|
|
|
|
public:
|
|
|
|
explicit WrappedBloom(int bits_per_key) :
|
|
|
|
filter_(NewBloomFilterPolicy(bits_per_key)),
|
|
|
|
counter_(0) {}
|
|
|
|
|
|
|
|
~WrappedBloom() { delete filter_; }
|
|
|
|
|
|
|
|
const char* Name() const override { return "WrappedRocksDbFilterPolicy"; }
|
|
|
|
|
|
|
|
void CreateFilter(const rocksdb::Slice* keys, int n, std::string* dst)
|
|
|
|
const override {
|
|
|
|
std::unique_ptr<rocksdb::Slice[]> user_keys(new rocksdb::Slice[n]);
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
user_keys[i] = convertKey(keys[i]);
|
|
|
|
}
|
|
|
|
return filter_->CreateFilter(user_keys.get(), n, dst);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KeyMayMatch(const rocksdb::Slice& key, const rocksdb::Slice& filter)
|
|
|
|
const override {
|
|
|
|
counter_++;
|
|
|
|
return filter_->KeyMayMatch(convertKey(key), filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t GetCounter() { return counter_; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
const FilterPolicy* filter_;
|
|
|
|
mutable uint32_t counter_;
|
|
|
|
|
2014-09-26 18:01:23 +02:00
|
|
|
rocksdb::Slice convertKey(const rocksdb::Slice& key) const {
|
2014-09-12 01:33:46 +02:00
|
|
|
return key;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, BloomFilterWrapper) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-09-12 01:33:46 +02:00
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
WrappedBloom* policy = new WrappedBloom(10);
|
|
|
|
table_options.filter_policy.reset(policy);
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-09-12 01:33:46 +02:00
|
|
|
|
|
|
|
const int maxKey = 10000;
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_OK(Put(1, Key(i), Key(i)));
|
|
|
|
}
|
|
|
|
// Add a large key to make the file contain wide range
|
|
|
|
ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
|
2014-09-12 23:05:07 +02:00
|
|
|
ASSERT_EQ(0U, policy->GetCounter());
|
2014-09-12 01:33:46 +02:00
|
|
|
Flush(1);
|
|
|
|
|
|
|
|
// Check if they can be found
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_EQ(Key(i), Get(1, Key(i)));
|
|
|
|
}
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
2014-09-12 23:05:07 +02:00
|
|
|
ASSERT_EQ(1U * maxKey, policy->GetCounter());
|
2014-09-12 01:33:46 +02:00
|
|
|
|
|
|
|
// Check if filter is useful
|
|
|
|
for (int i = 0; i < maxKey; i++) {
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(1, Key(i+33333)));
|
|
|
|
}
|
|
|
|
ASSERT_GE(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), maxKey*0.98);
|
2014-09-12 23:05:07 +02:00
|
|
|
ASSERT_EQ(2U * maxKey, policy->GetCounter());
|
2014-09-12 01:33:46 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, SnapshotFiles) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.write_buffer_size = 100000000; // Large write buffer
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2012-09-15 02:11:35 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
Random rnd(301);
|
2012-09-24 23:01:01 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Write 8MB (80 values, each 100K)
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
2013-08-08 00:20:41 +02:00
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int i = 0; i < 80; i++) {
|
|
|
|
values.push_back(RandomString(&rnd, 100000));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put((i < 40), Key(i), values[i]));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2012-09-24 23:01:01 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// assert that nothing makes it to disk yet.
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
2012-09-24 23:01:01 +02:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// get a file snapshot
|
|
|
|
uint64_t manifest_number = 0;
|
|
|
|
uint64_t manifest_size = 0;
|
|
|
|
std::vector<std::string> files;
|
|
|
|
dbfull()->DisableFileDeletions();
|
|
|
|
dbfull()->GetLiveFiles(files, &manifest_size);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
// CURRENT, MANIFEST, *.sst files (one for each CF)
|
|
|
|
ASSERT_EQ(files.size(), 4U);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
uint64_t number = 0;
|
|
|
|
FileType type;
|
|
|
|
|
|
|
|
// copy these files to a new snapshot directory
|
|
|
|
std::string snapdir = dbname_ + ".snapdir/";
|
2014-10-31 23:08:10 +01:00
|
|
|
ASSERT_OK(env_->CreateDirIfMissing(snapdir));
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
for (unsigned int i = 0; i < files.size(); i++) {
|
2013-11-09 00:23:46 +01:00
|
|
|
// our clients require that GetLiveFiles returns
|
|
|
|
// files with "/" as first character!
|
|
|
|
ASSERT_EQ(files[i][0], '/');
|
|
|
|
std::string src = dbname_ + files[i];
|
|
|
|
std::string dest = snapdir + files[i];
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
uint64_t size;
|
|
|
|
ASSERT_OK(env_->GetFileSize(src, &size));
|
|
|
|
|
|
|
|
// record the number and the size of the
|
|
|
|
// latest manifest file
|
|
|
|
if (ParseFileName(files[i].substr(1), &number, &type)) {
|
|
|
|
if (type == kDescriptorFile) {
|
|
|
|
if (number > manifest_number) {
|
|
|
|
manifest_number = number;
|
|
|
|
ASSERT_GE(size, manifest_size);
|
|
|
|
size = manifest_size; // copy only valid MANIFEST data
|
|
|
|
}
|
2012-09-24 23:01:01 +02:00
|
|
|
}
|
|
|
|
}
|
Refactor Recover() code
Summary:
This diff does two things:
* Rethinks how we call Recover() with read_only option. Before, we call it with pointer to memtable where we'd like to apply those changes to. This memtable is set in db_impl_readonly.cc and it's actually DBImpl::mem_. Why don't we just apply updates to mem_ right away? It seems more intuitive.
* Changes when we apply updates to manifest. Before, the process is to recover all the logs, flush it to sst files and then do one giant commit that atomically adds all recovered sst files and sets the next log number. This works good enough, but causes some small troubles for my column family approach, since I can't have one VersionEdit apply to more than single column family[1]. The change here is to commit the files recovered from logs right away. Here is the state of the world before the change:
1. Recover log 5, add new sst files to edit
2. Recover log 7, add new sst files to edit
3. Recover log 8, add new sst files to edit
4. Commit all added sst files to manifest and mark log files 5, 7 and 8 as recoverd (via SetLogNumber(9) function)
After the change, we'll do:
1. Recover log 5, commit the new sst files and set log 5 as recovered
2. Recover log 7, commit the new sst files and set log 7 as recovered
3. Recover log 8, commit the new sst files and set log 8 as recovered
The added (small) benefit is that if we fail after (2), the new recovery will only have to recover log 8. In previous case, we'll have to restart the recovery from the beginning. The bigger benefit will be to enable easier integration of multiple column families in Recovery code path.
[1] I'm happy to dicuss this decison, but I believe this is the cleanest way to go. It also makes backward compatibility much easier. We don't have a requirement of adding multiple column families atomically.
Test Plan: make check
Reviewers: dhruba, haobo, kailiu, sdong
Reviewed By: kailiu
CC: leveldb
Differential Revision: https://reviews.facebook.net/D15237
2014-01-22 19:45:26 +01:00
|
|
|
CopyFile(src, dest, size);
|
2012-09-24 23:01:01 +02:00
|
|
|
}
|
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// release file snapshot
|
|
|
|
dbfull()->DisableFileDeletions();
|
|
|
|
// overwrite one key, this key should not appear in the snapshot
|
|
|
|
std::vector<std::string> extras;
|
|
|
|
for (unsigned int i = 0; i < 1; i++) {
|
|
|
|
extras.push_back(RandomString(&rnd, 100000));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(0, Key(i), extras[i]));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
2012-11-01 18:50:08 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// verify that data in the snapshot are correct
|
2014-02-07 23:47:16 +01:00
|
|
|
std::vector<ColumnFamilyDescriptor> column_families;
|
|
|
|
column_families.emplace_back("default", ColumnFamilyOptions());
|
|
|
|
column_families.emplace_back("pikachu", ColumnFamilyOptions());
|
|
|
|
std::vector<ColumnFamilyHandle*> cf_handles;
|
2013-08-08 00:20:41 +02:00
|
|
|
DB* snapdb;
|
2014-02-07 23:47:16 +01:00
|
|
|
DBOptions opts;
|
2014-10-31 23:08:10 +01:00
|
|
|
opts.env = env_;
|
2013-08-08 00:20:41 +02:00
|
|
|
opts.create_if_missing = false;
|
2014-02-07 23:47:16 +01:00
|
|
|
Status stat =
|
|
|
|
DB::Open(opts, snapdir, column_families, &cf_handles, &snapdb);
|
2013-10-04 19:21:03 +02:00
|
|
|
ASSERT_OK(stat);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
ReadOptions roptions;
|
|
|
|
std::string val;
|
|
|
|
for (unsigned int i = 0; i < 80; i++) {
|
2014-02-07 23:47:16 +01:00
|
|
|
stat = snapdb->Get(roptions, cf_handles[i < 40], Key(i), &val);
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_EQ(values[i].compare(val), 0);
|
|
|
|
}
|
2014-02-07 23:47:16 +01:00
|
|
|
for (auto cfh : cf_handles) {
|
|
|
|
delete cfh;
|
|
|
|
}
|
2013-08-08 00:20:41 +02:00
|
|
|
delete snapdb;
|
|
|
|
|
|
|
|
// look at the new live files after we added an 'extra' key
|
|
|
|
// and after we took the first snapshot.
|
|
|
|
uint64_t new_manifest_number = 0;
|
|
|
|
uint64_t new_manifest_size = 0;
|
|
|
|
std::vector<std::string> newfiles;
|
|
|
|
dbfull()->DisableFileDeletions();
|
|
|
|
dbfull()->GetLiveFiles(newfiles, &new_manifest_size);
|
|
|
|
|
|
|
|
// find the new manifest file. assert that this manifest file is
|
|
|
|
// the same one as in the previous snapshot. But its size should be
|
|
|
|
// larger because we added an extra key after taking the
|
|
|
|
// previous shapshot.
|
|
|
|
for (unsigned int i = 0; i < newfiles.size(); i++) {
|
|
|
|
std::string src = dbname_ + "/" + newfiles[i];
|
|
|
|
// record the lognumber and the size of the
|
|
|
|
// latest manifest file
|
|
|
|
if (ParseFileName(newfiles[i].substr(1), &number, &type)) {
|
|
|
|
if (type == kDescriptorFile) {
|
|
|
|
if (number > new_manifest_number) {
|
|
|
|
uint64_t size;
|
|
|
|
new_manifest_number = number;
|
|
|
|
ASSERT_OK(env_->GetFileSize(src, &size));
|
|
|
|
ASSERT_GE(size, new_manifest_size);
|
|
|
|
}
|
2012-09-24 23:01:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_EQ(manifest_number, new_manifest_number);
|
|
|
|
ASSERT_GT(new_manifest_size, manifest_size);
|
2012-11-01 18:50:08 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// release file snapshot
|
|
|
|
dbfull()->DisableFileDeletions();
|
|
|
|
} while (ChangeCompactOptions());
|
2012-09-15 02:11:35 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactOnFlush) {
|
2015-02-02 23:49:22 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2015-02-02 23:49:22 +01:00
|
|
|
Options options = CurrentOptions(options_override);
|
2013-08-08 00:20:41 +02:00
|
|
|
options.purge_redundant_kvs_while_flush = true;
|
|
|
|
options.disable_auto_compactions = true;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v1");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v1 ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Write two new keys
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "a", "begin");
|
|
|
|
Put(1, "z", "end");
|
|
|
|
Flush(1);
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Case1: Delete followed by a put
|
2014-02-07 23:47:16 +01:00
|
|
|
Delete(1, "foo");
|
|
|
|
Put(1, "foo", "v2");
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, DEL, v1 ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// After the current memtable is flushed, the DEL should
|
|
|
|
// have been removed
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, v1 ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2 ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Case 2: Delete followed by another delete
|
2014-02-07 23:47:16 +01:00
|
|
|
Delete(1, "foo");
|
|
|
|
Delete(1, "foo");
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, DEL, v2 ]");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v2 ]");
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Case 3: Put followed by a delete
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v3");
|
|
|
|
Delete(1, "foo");
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v3 ]");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL ]");
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Case 4: Put followed by another Put
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v4");
|
|
|
|
Put(1, "foo", "v5");
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5, v4 ]");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5 ]");
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5 ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// clear database
|
2014-02-07 23:47:16 +01:00
|
|
|
Delete(1, "foo");
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Case 5: Put followed by snapshot followed by another Put
|
|
|
|
// Both puts should remain.
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v6");
|
2013-08-08 00:20:41 +02:00
|
|
|
const Snapshot* snapshot = db_->GetSnapshot();
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v7");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v7, v6 ]");
|
2013-08-08 00:20:41 +02:00
|
|
|
db_->ReleaseSnapshot(snapshot);
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// clear database
|
2014-02-07 23:47:16 +01:00
|
|
|
Delete(1, "foo");
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
|
|
|
nullptr);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]");
|
2013-02-28 23:09:30 +01:00
|
|
|
|
2013-08-08 00:20:41 +02:00
|
|
|
// Case 5: snapshot followed by a put followed by another Put
|
|
|
|
// Only the last put should remain.
|
|
|
|
const Snapshot* snapshot1 = db_->GetSnapshot();
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "foo", "v8");
|
|
|
|
Put(1, "foo", "v9");
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_EQ(AllEntriesFor("foo", 1), "[ v9 ]");
|
2013-08-08 00:20:41 +02:00
|
|
|
db_->ReleaseSnapshot(snapshot1);
|
|
|
|
} while (ChangeCompactOptions());
|
2013-02-28 23:09:30 +01:00
|
|
|
}
|
|
|
|
|
2014-04-10 06:17:14 +02:00
|
|
|
namespace {
|
2014-08-12 07:10:32 +02:00
|
|
|
std::vector<std::uint64_t> ListSpecificFiles(
|
|
|
|
Env* env, const std::string& path, const FileType expected_file_type) {
|
2012-11-26 22:56:45 +01:00
|
|
|
std::vector<std::string> files;
|
2014-09-09 20:18:50 +02:00
|
|
|
std::vector<uint64_t> file_numbers;
|
2012-11-26 22:56:45 +01:00
|
|
|
env->GetChildren(path, &files);
|
|
|
|
uint64_t number;
|
|
|
|
FileType type;
|
|
|
|
for (size_t i = 0; i < files.size(); ++i) {
|
|
|
|
if (ParseFileName(files[i], &number, &type)) {
|
2014-08-12 07:10:32 +02:00
|
|
|
if (type == expected_file_type) {
|
2014-09-09 20:18:50 +02:00
|
|
|
file_numbers.push_back(number);
|
2012-11-26 22:56:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-09-09 20:18:50 +02:00
|
|
|
return std::move(file_numbers);
|
2012-11-26 22:56:45 +01:00
|
|
|
}
|
2014-08-12 07:10:32 +02:00
|
|
|
|
|
|
|
std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path) {
|
|
|
|
return ListSpecificFiles(env, path, kTableFile);
|
|
|
|
}
|
2014-04-10 06:17:14 +02:00
|
|
|
} // namespace
|
2012-11-26 22:56:45 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FlushOneColumnFamily) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-08-12 07:10:32 +02:00
|
|
|
CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich",
|
|
|
|
"alyosha", "popovich"},
|
2014-10-29 20:00:01 +01:00
|
|
|
options);
|
2014-08-12 07:10:32 +02:00
|
|
|
|
|
|
|
ASSERT_OK(Put(0, "Default", "Default"));
|
|
|
|
ASSERT_OK(Put(1, "pikachu", "pikachu"));
|
|
|
|
ASSERT_OK(Put(2, "ilya", "ilya"));
|
|
|
|
ASSERT_OK(Put(3, "muromec", "muromec"));
|
|
|
|
ASSERT_OK(Put(4, "dobrynia", "dobrynia"));
|
|
|
|
ASSERT_OK(Put(5, "nikitich", "nikitich"));
|
|
|
|
ASSERT_OK(Put(6, "alyosha", "alyosha"));
|
|
|
|
ASSERT_OK(Put(7, "popovich", "popovich"));
|
|
|
|
|
2014-11-11 22:47:22 +01:00
|
|
|
for (int i = 0; i < 8; ++i) {
|
2014-08-12 07:10:32 +02:00
|
|
|
Flush(i);
|
|
|
|
auto tables = ListTableFiles(env_, dbname_);
|
2014-08-13 02:26:47 +02:00
|
|
|
ASSERT_EQ(tables.size(), i + 1U);
|
2014-08-12 07:10:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-09 20:18:50 +02:00
|
|
|
// In https://reviews.facebook.net/D20661 we change
|
|
|
|
// recovery behavior: previously for each log file each column family
|
|
|
|
// memtable was flushed, even it was empty. Now it's changed:
|
|
|
|
// we try to create the smallest number of table files by merging
|
|
|
|
// updates from multiple logs
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RecoverCheckFileAmountWithSmallWriteBuffer) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-09-09 20:18:50 +02:00
|
|
|
options.write_buffer_size = 5000000;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
2014-09-09 20:18:50 +02:00
|
|
|
|
|
|
|
// Since we will reopen DB with smaller write_buffer_size,
|
|
|
|
// each key will go to new SST file
|
|
|
|
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
|
|
|
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
|
|
|
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
|
|
|
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
|
|
|
|
|
|
|
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
|
|
|
// Make 'dobrynia' to be flushed and new WAL file to be created
|
|
|
|
ASSERT_OK(Put(2, Key(10), DummyString(7500000)));
|
|
|
|
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
|
|
|
{
|
|
|
|
auto tables = ListTableFiles(env_, dbname_);
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
|
2014-09-09 20:18:50 +02:00
|
|
|
// Make sure 'dobrynia' was flushed: check sst files amount
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
|
|
|
static_cast<uint64_t>(1));
|
2014-09-09 20:18:50 +02:00
|
|
|
}
|
|
|
|
// New WAL file
|
|
|
|
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
|
|
|
|
|
|
|
options.write_buffer_size = 10;
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
2014-10-29 20:00:42 +01:00
|
|
|
options);
|
2014-09-09 20:18:50 +02:00
|
|
|
{
|
|
|
|
// No inserts => default is empty
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
|
|
|
static_cast<uint64_t>(0));
|
2014-09-09 20:18:50 +02:00
|
|
|
// First 4 keys goes to separate SSTs + 1 more SST for 2 smaller keys
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
|
|
|
static_cast<uint64_t>(5));
|
2014-09-09 20:18:50 +02:00
|
|
|
// 1 SST for big key + 1 SST for small one
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
|
|
|
static_cast<uint64_t>(2));
|
2014-09-09 20:18:50 +02:00
|
|
|
// 1 SST for all keys
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(1));
|
2014-09-09 20:18:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// In https://reviews.facebook.net/D20661 we change
|
|
|
|
// recovery behavior: previously for each log file each column family
|
|
|
|
// memtable was flushed, even it wasn't empty. Now it's changed:
|
|
|
|
// we try to create the smallest number of table files by merging
|
|
|
|
// updates from multiple logs
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RecoverCheckFileAmount) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-09-09 20:18:50 +02:00
|
|
|
options.write_buffer_size = 100000;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
2014-09-09 20:18:50 +02:00
|
|
|
|
|
|
|
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
|
|
|
|
|
|
|
// Make 'nikitich' memtable to be flushed
|
|
|
|
ASSERT_OK(Put(3, Key(10), DummyString(1002400)));
|
|
|
|
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
|
|
|
// 4 memtable are not flushed, 1 sst file
|
|
|
|
{
|
|
|
|
auto tables = ListTableFiles(env_, dbname_);
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(1));
|
2014-09-09 20:18:50 +02:00
|
|
|
}
|
|
|
|
// Memtable for 'nikitich' has flushed, new WAL file has opened
|
|
|
|
// 4 memtable still not flushed
|
|
|
|
|
|
|
|
// Write to new WAL file
|
|
|
|
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
|
|
|
|
|
|
|
// Fill up 'nikitich' one more time
|
|
|
|
ASSERT_OK(Put(3, Key(10), DummyString(1002400)));
|
|
|
|
// make it flush
|
|
|
|
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
|
|
|
// There are still 4 memtable not flushed, and 2 sst tables
|
|
|
|
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
|
|
|
|
|
|
|
{
|
|
|
|
auto tables = ListTableFiles(env_, dbname_);
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(tables.size(), static_cast<size_t>(2));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(2));
|
2014-09-09 20:18:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
2014-10-29 20:00:42 +01:00
|
|
|
options);
|
2014-09-09 20:18:50 +02:00
|
|
|
{
|
|
|
|
std::vector<uint64_t> table_files = ListTableFiles(env_, dbname_);
|
|
|
|
// Check, that records for 'default', 'dobrynia' and 'pikachu' from
|
|
|
|
// first, second and third WALs went to the same SST.
|
|
|
|
// So, there is 6 SSTs: three for 'nikitich', one for 'default', one for
|
|
|
|
// 'dobrynia', one for 'pikachu'
|
2014-09-10 03:42:35 +02:00
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(3));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
|
|
|
static_cast<uint64_t>(1));
|
2014-09-09 20:18:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, SharedWriteBuffer) {
|
2015-01-29 00:30:02 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-12-02 21:09:20 +01:00
|
|
|
options.db_write_buffer_size = 100000; // this is the real limit
|
|
|
|
options.write_buffer_size = 500000; // this is never hit
|
|
|
|
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
|
|
|
|
|
|
|
// Trigger a flush on every CF
|
|
|
|
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
|
|
|
ASSERT_OK(Put(3, Key(1), DummyString(90000)));
|
|
|
|
ASSERT_OK(Put(2, Key(2), DummyString(20000)));
|
|
|
|
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
|
|
|
{
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flush 'dobrynia' and 'nikitich'
|
|
|
|
ASSERT_OK(Put(2, Key(2), DummyString(50000)));
|
|
|
|
ASSERT_OK(Put(3, Key(2), DummyString(40000)));
|
|
|
|
ASSERT_OK(Put(2, Key(3), DummyString(20000)));
|
|
|
|
ASSERT_OK(Put(3, Key(2), DummyString(40000)));
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
|
|
|
{
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
|
|
|
static_cast<uint64_t>(2));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(2));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make 'dobrynia' and 'nikitich' both take up 40% of space
|
|
|
|
// When 'pikachu' puts us over 100%, all 3 flush.
|
|
|
|
ASSERT_OK(Put(2, Key(2), DummyString(40000)));
|
|
|
|
ASSERT_OK(Put(1, Key(2), DummyString(20000)));
|
|
|
|
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
|
|
|
{
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
|
|
|
static_cast<uint64_t>(1));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
|
|
|
static_cast<uint64_t>(2));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
|
|
|
static_cast<uint64_t>(3));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(3));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Some remaining writes so 'default' and 'nikitich' flush on closure.
|
|
|
|
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
|
|
|
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
|
|
|
options);
|
|
|
|
{
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
|
|
|
static_cast<uint64_t>(2));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
|
|
|
static_cast<uint64_t>(2));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
|
|
|
static_cast<uint64_t>(3));
|
|
|
|
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
|
|
|
static_cast<uint64_t>(4));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PurgeInfoLogs) {
|
2014-08-14 19:05:16 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.keep_log_file_num = 5;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
for (int mode = 0; mode <= 1; mode++) {
|
|
|
|
if (mode == 1) {
|
|
|
|
options.db_log_dir = dbname_ + "_logs";
|
|
|
|
env_->CreateDirIfMissing(options.db_log_dir);
|
|
|
|
} else {
|
|
|
|
options.db_log_dir = "";
|
|
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++) {
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-08-14 19:05:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> files;
|
|
|
|
env_->GetChildren(options.db_log_dir.empty() ? dbname_ : options.db_log_dir,
|
|
|
|
&files);
|
|
|
|
int info_log_count = 0;
|
|
|
|
for (std::string file : files) {
|
|
|
|
if (file.find("LOG") != std::string::npos) {
|
|
|
|
info_log_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ASSERT_EQ(5, info_log_count);
|
2014-08-20 19:29:32 +02:00
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
2014-09-11 03:46:09 +02:00
|
|
|
// For mode (1), test DestroyDB() to delete all the logs under DB dir.
|
2014-08-20 19:29:32 +02:00
|
|
|
// For mode (2), no info log file should have been put under DB dir.
|
|
|
|
std::vector<std::string> db_files;
|
|
|
|
env_->GetChildren(dbname_, &db_files);
|
|
|
|
for (std::string file : db_files) {
|
|
|
|
ASSERT_TRUE(file.find("LOG") == std::string::npos);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == 1) {
|
|
|
|
// Cleaning up
|
|
|
|
env_->GetChildren(options.db_log_dir, &files);
|
|
|
|
for (std::string file : files) {
|
|
|
|
env_->DeleteFile(options.db_log_dir + "/" + file);
|
|
|
|
}
|
|
|
|
env_->DeleteDir(options.db_log_dir);
|
|
|
|
}
|
2014-08-14 19:05:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-10 06:17:14 +02:00
|
|
|
namespace {
|
2013-10-25 04:09:02 +02:00
|
|
|
SequenceNumber ReadRecords(
|
|
|
|
std::unique_ptr<TransactionLogIterator>& iter,
|
|
|
|
int& count) {
|
|
|
|
count = 0;
|
2013-03-21 23:12:35 +01:00
|
|
|
SequenceNumber lastSequence = 0;
|
2013-10-25 04:09:02 +02:00
|
|
|
BatchResult res;
|
2013-03-21 23:12:35 +01:00
|
|
|
while (iter->Valid()) {
|
2013-10-25 04:09:02 +02:00
|
|
|
res = iter->GetBatch();
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_TRUE(res.sequence > lastSequence);
|
2013-10-25 04:09:02 +02:00
|
|
|
++count;
|
2013-03-21 23:12:35 +01:00
|
|
|
lastSequence = res.sequence;
|
rocksdb: Replace ASSERT* with EXPECT* in functions that does not return void value
Summary:
gtest does not use exceptions to fail a unit test by design, and `ASSERT*`s are implemented using `return`. As a consequence we cannot use `ASSERT*` in a function that does not return `void` value ([[ https://code.google.com/p/googletest/wiki/AdvancedGuide#Assertion_Placement | 1]]), and have to fix our existing code. This diff does this in a generic way, with no manual changes.
In order to detect all existing `ASSERT*` that are used in functions that doesn't return void value, I change the code to generate compile errors for such cases.
In `util/testharness.h` I defined `EXPECT*` assertions, the same way as `ASSERT*`, and redefined `ASSERT*` to return `void`. Then executed:
```lang=bash
% USE_CLANG=1 make all -j55 -k 2> build.log
% perl -naF: -e 'print "-- -number=".$F[1]." ".$F[0]."\n" if /: error:/' \
build.log | xargs -L 1 perl -spi -e 's/ASSERT/EXPECT/g if $. == $number'
% make format
```
After that I reverted back change to `ASSERT*` in `util/testharness.h`. But preserved introduced `EXPECT*`, which is the same as `ASSERT*`. This will be deleted once switched to gtest.
This diff is independent and contains manual changes only in `util/testharness.h`.
Test Plan:
Make sure all tests are passing.
```lang=bash
% USE_CLANG=1 make check
```
Reviewers: igor, lgalanis, sdong, yufei.zhu, rven, meyering
Reviewed By: meyering
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D33333
2015-03-17 04:52:32 +01:00
|
|
|
EXPECT_OK(iter->status());
|
2013-03-21 23:12:35 +01:00
|
|
|
iter->Next();
|
|
|
|
}
|
2013-10-25 04:09:02 +02:00
|
|
|
return res.sequence;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExpectRecords(
|
|
|
|
const int expected_no_records,
|
|
|
|
std::unique_ptr<TransactionLogIterator>& iter) {
|
|
|
|
int num_records;
|
|
|
|
ReadRecords(iter, num_records);
|
|
|
|
ASSERT_EQ(num_records, expected_no_records);
|
2013-03-21 23:12:35 +01:00
|
|
|
}
|
2014-04-10 06:17:14 +02:00
|
|
|
} // namespace
|
2013-03-21 23:12:35 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TransactionLogIterator) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = OptionsForLogIterTest();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(0, "key1", DummyString(1024));
|
|
|
|
Put(1, "key2", DummyString(1024));
|
|
|
|
Put(1, "key2", DummyString(1024));
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3U);
|
|
|
|
{
|
|
|
|
auto iter = OpenTransactionLogIter(0);
|
|
|
|
ExpectRecords(3, iter);
|
|
|
|
}
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
env_->SleepForMicroseconds(2 * 1000 * 1000);
|
|
|
|
{
|
|
|
|
Put(0, "key4", DummyString(1024));
|
|
|
|
Put(1, "key5", DummyString(1024));
|
|
|
|
Put(0, "key6", DummyString(1024));
|
2013-08-08 00:20:41 +02:00
|
|
|
}
|
|
|
|
{
|
|
|
|
auto iter = OpenTransactionLogIter(0);
|
|
|
|
ExpectRecords(6, iter);
|
|
|
|
}
|
|
|
|
} while (ChangeCompactOptions());
|
2012-11-30 02:28:37 +01:00
|
|
|
}
|
|
|
|
|
2014-04-21 20:08:30 +02:00
|
|
|
#ifndef NDEBUG // sync point is not included with DNDEBUG build
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TransactionLogIteratorRace) {
|
2014-05-13 02:50:21 +02:00
|
|
|
static const int LOG_ITERATOR_RACE_TEST_COUNT = 2;
|
2014-10-30 01:43:37 +01:00
|
|
|
static const char* sync_points[LOG_ITERATOR_RACE_TEST_COUNT][4] = {
|
|
|
|
{"WalManager::GetSortedWalFiles:1", "WalManager::PurgeObsoleteFiles:1",
|
|
|
|
"WalManager::PurgeObsoleteFiles:2", "WalManager::GetSortedWalFiles:2"},
|
|
|
|
{"WalManager::GetSortedWalsOfType:1",
|
|
|
|
"WalManager::PurgeObsoleteFiles:1",
|
|
|
|
"WalManager::PurgeObsoleteFiles:2",
|
|
|
|
"WalManager::GetSortedWalsOfType:2"}};
|
2014-05-13 02:50:21 +02:00
|
|
|
for (int test = 0; test < LOG_ITERATOR_RACE_TEST_COUNT; ++test) {
|
|
|
|
// Setup sync point dependency to reproduce the race condition of
|
|
|
|
// a log file moved to archived dir, in the middle of GetSortedWalFiles
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
|
|
|
{ { sync_points[test][0], sync_points[test][1] },
|
|
|
|
{ sync_points[test][2], sync_points[test][3] },
|
|
|
|
});
|
|
|
|
|
|
|
|
do {
|
|
|
|
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
Options options = OptionsForLogIterTest();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-05-13 02:50:21 +02:00
|
|
|
Put("key1", DummyString(1024));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
Put("key2", DummyString(1024));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
Put("key3", DummyString(1024));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
Put("key4", DummyString(1024));
|
|
|
|
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4U);
|
|
|
|
|
|
|
|
{
|
|
|
|
auto iter = OpenTransactionLogIter(0);
|
|
|
|
ExpectRecords(4, iter);
|
|
|
|
}
|
2014-03-24 05:49:14 +01:00
|
|
|
|
2014-05-13 02:50:21 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
// trigger async flush, and log move. Well, log move will
|
|
|
|
// wait until the GetSortedWalFiles:1 to reproduce the race
|
|
|
|
// condition
|
|
|
|
FlushOptions flush_options;
|
|
|
|
flush_options.wait = false;
|
|
|
|
dbfull()->Flush(flush_options);
|
|
|
|
|
|
|
|
// "key5" would be written in a new memtable and log
|
|
|
|
Put("key5", DummyString(1024));
|
|
|
|
{
|
|
|
|
// this iter would miss "key4" if not fixed
|
|
|
|
auto iter = OpenTransactionLogIter(0);
|
|
|
|
ExpectRecords(5, iter);
|
|
|
|
}
|
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
2014-03-24 05:49:14 +01:00
|
|
|
}
|
2014-04-21 20:08:30 +02:00
|
|
|
#endif
|
2014-03-24 05:49:14 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TransactionLogIteratorStallAtLastRecord) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = OptionsForLogIterTest();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2013-08-08 00:20:41 +02:00
|
|
|
Put("key1", DummyString(1024));
|
|
|
|
auto iter = OpenTransactionLogIter(0);
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
Put("key2", DummyString(1024));
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
} while (ChangeCompactOptions());
|
2013-03-28 21:13:35 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TransactionLogIteratorCheckAfterRestart) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = OptionsForLogIterTest();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2013-08-08 00:20:41 +02:00
|
|
|
Put("key1", DummyString(1024));
|
|
|
|
Put("key2", DummyString(1023));
|
|
|
|
dbfull()->Flush(FlushOptions());
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2013-08-08 00:20:41 +02:00
|
|
|
auto iter = OpenTransactionLogIter(0);
|
|
|
|
ExpectRecords(2, iter);
|
|
|
|
} while (ChangeCompactOptions());
|
2013-04-03 02:18:27 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TransactionLogIteratorCorruptedLog) {
|
2013-10-25 04:09:02 +02:00
|
|
|
do {
|
|
|
|
Options options = OptionsForLogIterTest();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2013-10-25 04:09:02 +02:00
|
|
|
for (int i = 0; i < 1024; i++) {
|
2014-11-25 05:44:49 +01:00
|
|
|
Put("key"+ToString(i), DummyString(10));
|
2013-10-25 04:09:02 +02:00
|
|
|
}
|
|
|
|
dbfull()->Flush(FlushOptions());
|
|
|
|
// Corrupt this log to create a gap
|
|
|
|
rocksdb::VectorLogPtr wal_files;
|
|
|
|
ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
|
2014-10-31 23:08:10 +01:00
|
|
|
const auto logfile_path = dbname_ + "/" + wal_files.front()->PathName();
|
|
|
|
if (mem_env_) {
|
|
|
|
mem_env_->Truncate(logfile_path, wal_files.front()->SizeFileBytes() / 2);
|
|
|
|
} else {
|
|
|
|
ASSERT_EQ(0, truncate(logfile_path.c_str(),
|
|
|
|
wal_files.front()->SizeFileBytes() / 2));
|
|
|
|
}
|
|
|
|
|
2013-10-25 04:09:02 +02:00
|
|
|
// Insert a new entry to a new log file
|
|
|
|
Put("key1025", DummyString(10));
|
|
|
|
// Try to read from the beginning. Should stop before the gap and read less
|
|
|
|
// than 1025 entries
|
|
|
|
auto iter = OpenTransactionLogIter(0);
|
|
|
|
int count;
|
2014-11-11 22:47:22 +01:00
|
|
|
SequenceNumber last_sequence_read = ReadRecords(iter, count);
|
|
|
|
ASSERT_LT(last_sequence_read, 1025U);
|
2013-10-25 04:09:02 +02:00
|
|
|
// Try to read past the gap, should be able to seek to key1025
|
|
|
|
auto iter2 = OpenTransactionLogIter(last_sequence_read + 1);
|
|
|
|
ExpectRecords(1, iter2);
|
|
|
|
} while (ChangeCompactOptions());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TransactionLogIteratorBatchOperations) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
|
|
|
Options options = OptionsForLogIterTest();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-08-08 00:20:41 +02:00
|
|
|
WriteBatch batch;
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Put(handles_[1], "key1", DummyString(1024));
|
|
|
|
batch.Put(handles_[0], "key2", DummyString(1024));
|
|
|
|
batch.Put(handles_[1], "key3", DummyString(1024));
|
|
|
|
batch.Delete(handles_[0], "key2");
|
2013-08-08 00:20:41 +02:00
|
|
|
dbfull()->Write(WriteOptions(), &batch);
|
2014-02-07 23:47:16 +01:00
|
|
|
Flush(1);
|
|
|
|
Flush(0);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
Put(1, "key4", DummyString(1024));
|
2013-08-08 00:20:41 +02:00
|
|
|
auto iter = OpenTransactionLogIter(3);
|
2013-10-14 00:28:24 +02:00
|
|
|
ExpectRecords(2, iter);
|
2013-08-08 00:20:41 +02:00
|
|
|
} while (ChangeCompactOptions());
|
2013-04-09 01:28:09 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TransactionLogIteratorBlobs) {
|
2013-08-15 01:32:46 +02:00
|
|
|
Options options = OptionsForLogIterTest();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2013-08-15 01:32:46 +02:00
|
|
|
{
|
|
|
|
WriteBatch batch;
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Put(handles_[1], "key1", DummyString(1024));
|
|
|
|
batch.Put(handles_[0], "key2", DummyString(1024));
|
2013-08-15 01:32:46 +02:00
|
|
|
batch.PutLogData(Slice("blob1"));
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Put(handles_[1], "key3", DummyString(1024));
|
2013-08-15 01:32:46 +02:00
|
|
|
batch.PutLogData(Slice("blob2"));
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Delete(handles_[0], "key2");
|
2013-08-15 01:32:46 +02:00
|
|
|
dbfull()->Write(WriteOptions(), &batch);
|
2014-10-29 20:00:42 +01:00
|
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
2013-08-15 01:32:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
auto res = OpenTransactionLogIter(0)->GetBatch();
|
|
|
|
struct Handler : public WriteBatch::Handler {
|
|
|
|
std::string seen;
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status PutCF(uint32_t cf, const Slice& key,
|
|
|
|
const Slice& value) override {
|
2014-11-25 05:44:49 +01:00
|
|
|
seen += "Put(" + ToString(cf) + ", " + key.ToString() + ", " +
|
|
|
|
ToString(value.size()) + ")";
|
2014-02-26 02:30:54 +01:00
|
|
|
return Status::OK();
|
2013-08-15 01:32:46 +02:00
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status MergeCF(uint32_t cf, const Slice& key,
|
|
|
|
const Slice& value) override {
|
2014-11-25 05:44:49 +01:00
|
|
|
seen += "Merge(" + ToString(cf) + ", " + key.ToString() + ", " +
|
|
|
|
ToString(value.size()) + ")";
|
2014-02-26 02:30:54 +01:00
|
|
|
return Status::OK();
|
2013-08-15 01:32:46 +02:00
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void LogData(const Slice& blob) override {
|
2013-08-15 01:32:46 +02:00
|
|
|
seen += "LogData(" + blob.ToString() + ")";
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status DeleteCF(uint32_t cf, const Slice& key) override {
|
2014-11-25 05:44:49 +01:00
|
|
|
seen += "Delete(" + ToString(cf) + ", " + key.ToString() + ")";
|
2014-02-26 02:30:54 +01:00
|
|
|
return Status::OK();
|
2013-08-15 01:32:46 +02:00
|
|
|
}
|
|
|
|
} handler;
|
|
|
|
res.writeBatchPtr->Iterate(&handler);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(
|
|
|
|
"Put(1, key1, 1024)"
|
|
|
|
"Put(0, key2, 1024)"
|
|
|
|
"LogData(blob1)"
|
|
|
|
"Put(1, key3, 1024)"
|
|
|
|
"LogData(blob2)"
|
|
|
|
"Delete(0, key2)",
|
|
|
|
handler.seen);
|
2013-08-15 01:32:46 +02:00
|
|
|
}
|
|
|
|
|
2011-05-28 02:53:58 +02:00
|
|
|
// Multi-threaded test:
|
|
|
|
namespace {
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
static const int kColumnFamilies = 10;
|
|
|
|
static const int kNumThreads = 10;
|
2011-05-28 02:53:58 +02:00
|
|
|
static const int kTestSeconds = 10;
|
|
|
|
static const int kNumKeys = 1000;
|
|
|
|
|
|
|
|
struct MTState {
|
|
|
|
DBTest* test;
|
2014-10-27 22:50:21 +01:00
|
|
|
std::atomic<bool> stop;
|
|
|
|
std::atomic<int> counter[kNumThreads];
|
|
|
|
std::atomic<bool> thread_done[kNumThreads];
|
2011-05-28 02:53:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
struct MTThread {
|
|
|
|
MTState* state;
|
|
|
|
int id;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void MTThreadBody(void* arg) {
|
|
|
|
MTThread* t = reinterpret_cast<MTThread*>(arg);
|
2012-01-25 23:56:52 +01:00
|
|
|
int id = t->id;
|
2011-05-28 02:53:58 +02:00
|
|
|
DB* db = t->state->test->db_;
|
2014-10-27 22:50:21 +01:00
|
|
|
int counter = 0;
|
2012-01-25 23:56:52 +01:00
|
|
|
fprintf(stderr, "... starting thread %d\n", id);
|
|
|
|
Random rnd(1000 + id);
|
2011-05-28 02:53:58 +02:00
|
|
|
char valbuf[1500];
|
2014-10-27 22:50:21 +01:00
|
|
|
while (t->state->stop.load(std::memory_order_acquire) == false) {
|
|
|
|
t->state->counter[id].store(counter, std::memory_order_release);
|
2011-05-28 02:53:58 +02:00
|
|
|
|
|
|
|
int key = rnd.Uniform(kNumKeys);
|
|
|
|
char keybuf[20];
|
|
|
|
snprintf(keybuf, sizeof(keybuf), "%016d", key);
|
|
|
|
|
|
|
|
if (rnd.OneIn(2)) {
|
2014-02-07 23:47:16 +01:00
|
|
|
// Write values of the form <key, my id, counter, cf, unique_id>.
|
|
|
|
// into each of the CFs
|
2011-05-28 02:53:58 +02:00
|
|
|
// We add some padding for force compactions.
|
2014-02-07 23:47:16 +01:00
|
|
|
int unique_id = rnd.Uniform(1000000);
|
2014-08-19 00:19:17 +02:00
|
|
|
|
|
|
|
// Half of the time directly use WriteBatch. Half of the time use
|
|
|
|
// WriteBatchWithIndex.
|
|
|
|
if (rnd.OneIn(2)) {
|
|
|
|
WriteBatch batch;
|
|
|
|
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
|
|
|
snprintf(valbuf, sizeof(valbuf), "%d.%d.%d.%d.%-1000d", key, id,
|
|
|
|
static_cast<int>(counter), cf, unique_id);
|
|
|
|
batch.Put(t->state->test->handles_[cf], Slice(keybuf), Slice(valbuf));
|
|
|
|
}
|
|
|
|
ASSERT_OK(db->Write(WriteOptions(), &batch));
|
|
|
|
} else {
|
|
|
|
WriteBatchWithIndex batch(db->GetOptions().comparator);
|
|
|
|
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
|
|
|
snprintf(valbuf, sizeof(valbuf), "%d.%d.%d.%d.%-1000d", key, id,
|
|
|
|
static_cast<int>(counter), cf, unique_id);
|
|
|
|
batch.Put(t->state->test->handles_[cf], Slice(keybuf), Slice(valbuf));
|
|
|
|
}
|
|
|
|
ASSERT_OK(db->Write(WriteOptions(), batch.GetWriteBatch()));
|
2014-02-07 23:47:16 +01:00
|
|
|
}
|
2011-05-28 02:53:58 +02:00
|
|
|
} else {
|
2014-02-07 23:47:16 +01:00
|
|
|
// Read a value and verify that it matches the pattern written above
|
|
|
|
// and that writes to all column families were atomic (unique_id is the
|
|
|
|
// same)
|
|
|
|
std::vector<Slice> keys(kColumnFamilies, Slice(keybuf));
|
|
|
|
std::vector<std::string> values;
|
|
|
|
std::vector<Status> statuses =
|
|
|
|
db->MultiGet(ReadOptions(), t->state->test->handles_, keys, &values);
|
|
|
|
Status s = statuses[0];
|
|
|
|
// all statuses have to be the same
|
|
|
|
for (size_t i = 1; i < statuses.size(); ++i) {
|
|
|
|
// they are either both ok or both not-found
|
|
|
|
ASSERT_TRUE((s.ok() && statuses[i].ok()) ||
|
|
|
|
(s.IsNotFound() && statuses[i].IsNotFound()));
|
|
|
|
}
|
2011-05-28 02:53:58 +02:00
|
|
|
if (s.IsNotFound()) {
|
|
|
|
// Key has not yet been written
|
|
|
|
} else {
|
|
|
|
// Check that the writer thread counter is >= the counter in the value
|
|
|
|
ASSERT_OK(s);
|
2014-02-07 23:47:16 +01:00
|
|
|
int unique_id = -1;
|
|
|
|
for (int i = 0; i < kColumnFamilies; ++i) {
|
|
|
|
int k, w, c, cf, u;
|
|
|
|
ASSERT_EQ(5, sscanf(values[i].c_str(), "%d.%d.%d.%d.%d", &k, &w,
|
|
|
|
&c, &cf, &u))
|
|
|
|
<< values[i];
|
|
|
|
ASSERT_EQ(k, key);
|
|
|
|
ASSERT_GE(w, 0);
|
|
|
|
ASSERT_LT(w, kNumThreads);
|
2014-10-27 22:50:21 +01:00
|
|
|
ASSERT_LE(c, t->state->counter[w].load(std::memory_order_acquire));
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_EQ(cf, i);
|
|
|
|
if (i == 0) {
|
|
|
|
unique_id = u;
|
|
|
|
} else {
|
|
|
|
// this checks that updates across column families happened
|
|
|
|
// atomically -- all unique ids are the same
|
|
|
|
ASSERT_EQ(u, unique_id);
|
|
|
|
}
|
|
|
|
}
|
2011-05-28 02:53:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
counter++;
|
|
|
|
}
|
2014-10-27 22:50:21 +01:00
|
|
|
t->state->thread_done[id].store(true, std::memory_order_release);
|
2012-01-25 23:56:52 +01:00
|
|
|
fprintf(stderr, "... stopping thread %d after %d ops\n", id, int(counter));
|
2011-05-28 02:53:58 +02:00
|
|
|
}
|
|
|
|
|
2011-10-31 18:22:06 +01:00
|
|
|
} // namespace
|
2011-05-28 02:53:58 +02:00
|
|
|
|
2015-03-19 02:18:12 +01:00
|
|
|
class MultiThreadedDBTest : public DBTest,
|
|
|
|
public ::testing::WithParamInterface<int> {
|
|
|
|
public:
|
|
|
|
virtual void SetUp() override { option_config_ = GetParam(); }
|
|
|
|
|
|
|
|
static std::vector<int> GenerateOptionConfigs() {
|
|
|
|
std::vector<int> optionConfigs;
|
|
|
|
for (int optionConfig = kDefault; optionConfig < kEnd; ++optionConfig) {
|
|
|
|
// skip as HashCuckooRep does not support snapshot
|
|
|
|
if (optionConfig != kHashCuckoo) {
|
|
|
|
optionConfigs.push_back(optionConfig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return optionConfigs;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
2015-02-02 23:49:22 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2015-03-19 02:18:12 +01:00
|
|
|
std::vector<std::string> cfs;
|
|
|
|
for (int i = 1; i < kColumnFamilies; ++i) {
|
|
|
|
cfs.push_back(ToString(i));
|
|
|
|
}
|
|
|
|
CreateAndReopenWithCF(cfs, CurrentOptions(options_override));
|
|
|
|
// Initialize state
|
|
|
|
MTState mt;
|
|
|
|
mt.test = this;
|
|
|
|
mt.stop.store(false, std::memory_order_release);
|
|
|
|
for (int id = 0; id < kNumThreads; id++) {
|
|
|
|
mt.counter[id].store(0, std::memory_order_release);
|
|
|
|
mt.thread_done[id].store(false, std::memory_order_release);
|
|
|
|
}
|
2011-05-28 02:53:58 +02:00
|
|
|
|
2015-03-19 02:18:12 +01:00
|
|
|
// Start threads
|
|
|
|
MTThread thread[kNumThreads];
|
|
|
|
for (int id = 0; id < kNumThreads; id++) {
|
|
|
|
thread[id].state = &mt;
|
|
|
|
thread[id].id = id;
|
|
|
|
env_->StartThread(MTThreadBody, &thread[id]);
|
|
|
|
}
|
2011-05-28 02:53:58 +02:00
|
|
|
|
2015-03-19 02:18:12 +01:00
|
|
|
// Let them run for a while
|
|
|
|
env_->SleepForMicroseconds(kTestSeconds * 1000000);
|
2011-05-28 02:53:58 +02:00
|
|
|
|
2015-03-19 02:18:12 +01:00
|
|
|
// Stop the threads and wait for them to finish
|
|
|
|
mt.stop.store(true, std::memory_order_release);
|
|
|
|
for (int id = 0; id < kNumThreads; id++) {
|
|
|
|
while (mt.thread_done[id].load(std::memory_order_acquire) == false) {
|
|
|
|
env_->SleepForMicroseconds(100000);
|
2011-05-28 02:53:58 +02:00
|
|
|
}
|
2015-03-19 02:18:12 +01:00
|
|
|
}
|
2011-05-28 02:53:58 +02:00
|
|
|
}
|
|
|
|
|
2015-03-19 02:18:12 +01:00
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
|
|
MultiThreaded, MultiThreadedDBTest,
|
|
|
|
::testing::ValuesIn(MultiThreadedDBTest::GenerateOptionConfigs()));
|
|
|
|
|
2014-01-14 23:49:31 +01:00
|
|
|
// Group commit test:
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
static const int kGCNumThreads = 4;
|
|
|
|
static const int kGCNumKeys = 1000;
|
|
|
|
|
|
|
|
struct GCThread {
|
|
|
|
DB* db;
|
|
|
|
int id;
|
|
|
|
std::atomic<bool> done;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void GCThreadBody(void* arg) {
|
|
|
|
GCThread* t = reinterpret_cast<GCThread*>(arg);
|
|
|
|
int id = t->id;
|
|
|
|
DB* db = t->db;
|
|
|
|
WriteOptions wo;
|
|
|
|
|
|
|
|
for (int i = 0; i < kGCNumKeys; ++i) {
|
2014-11-25 05:44:49 +01:00
|
|
|
std::string kv(ToString(i + id * kGCNumKeys));
|
2014-01-14 23:49:31 +01:00
|
|
|
ASSERT_OK(db->Put(wo, kv, kv));
|
|
|
|
}
|
|
|
|
t->done = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GroupCommitTest) {
|
2014-01-14 23:49:31 +01:00
|
|
|
do {
|
2014-02-07 23:47:16 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-11-06 19:14:47 +01:00
|
|
|
options.env = env_;
|
|
|
|
env_->log_write_slowdown_.store(100);
|
2014-02-07 23:47:16 +01:00
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-02-07 23:47:16 +01:00
|
|
|
|
2014-01-14 23:49:31 +01:00
|
|
|
// Start threads
|
|
|
|
GCThread thread[kGCNumThreads];
|
|
|
|
for (int id = 0; id < kGCNumThreads; id++) {
|
|
|
|
thread[id].id = id;
|
|
|
|
thread[id].db = db_;
|
|
|
|
thread[id].done = false;
|
|
|
|
env_->StartThread(GCThreadBody, &thread[id]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int id = 0; id < kGCNumThreads; id++) {
|
|
|
|
while (thread[id].done == false) {
|
|
|
|
env_->SleepForMicroseconds(100000);
|
|
|
|
}
|
|
|
|
}
|
2014-11-06 19:14:47 +01:00
|
|
|
env_->log_write_slowdown_.store(0);
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_GT(TestGetTickerCount(options, WRITE_DONE_BY_OTHER), 0);
|
2014-01-14 23:49:31 +01:00
|
|
|
|
|
|
|
std::vector<std::string> expected_db;
|
|
|
|
for (int i = 0; i < kGCNumThreads * kGCNumKeys; ++i) {
|
2014-11-25 05:44:49 +01:00
|
|
|
expected_db.push_back(ToString(i));
|
2014-01-14 23:49:31 +01:00
|
|
|
}
|
|
|
|
sort(expected_db.begin(), expected_db.end());
|
|
|
|
|
|
|
|
Iterator* itr = db_->NewIterator(ReadOptions());
|
|
|
|
itr->SeekToFirst();
|
|
|
|
for (auto x : expected_db) {
|
|
|
|
ASSERT_TRUE(itr->Valid());
|
|
|
|
ASSERT_EQ(itr->key().ToString(), x);
|
|
|
|
ASSERT_EQ(itr->value().ToString(), x);
|
|
|
|
itr->Next();
|
|
|
|
}
|
|
|
|
ASSERT_TRUE(!itr->Valid());
|
2014-01-15 00:41:30 +01:00
|
|
|
delete itr;
|
2014-01-14 23:49:31 +01:00
|
|
|
|
2015-06-02 21:35:12 +02:00
|
|
|
HistogramData hist_data = {0};
|
|
|
|
options.statistics->histogramData(DB_WRITE, &hist_data);
|
|
|
|
ASSERT_GT(hist_data.average, 0.0);
|
2014-04-25 21:21:34 +02:00
|
|
|
} while (ChangeOptions(kSkipNoSeekToLast));
|
2014-01-14 23:49:31 +01:00
|
|
|
}
|
|
|
|
|
2011-05-21 04:17:43 +02:00
|
|
|
namespace {
|
|
|
|
typedef std::map<std::string, std::string> KVMap;
|
|
|
|
}
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
class ModelDB: public DB {
|
|
|
|
public:
|
2011-05-21 04:17:43 +02:00
|
|
|
class ModelSnapshot : public Snapshot {
|
|
|
|
public:
|
|
|
|
KVMap map_;
|
2015-01-30 03:22:43 +01:00
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual SequenceNumber GetSequenceNumber() const override {
|
2015-01-30 03:22:43 +01:00
|
|
|
// no need to call this
|
|
|
|
assert(false);
|
|
|
|
return 0;
|
|
|
|
}
|
2011-05-21 04:17:43 +02:00
|
|
|
};
|
|
|
|
|
2014-02-11 02:04:44 +01:00
|
|
|
explicit ModelDB(const Options& options) : options_(options) {}
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::Put;
|
2014-02-11 02:04:44 +01:00
|
|
|
virtual Status Put(const WriteOptions& o, ColumnFamilyHandle* cf,
|
2015-02-26 20:28:41 +01:00
|
|
|
const Slice& k, const Slice& v) override {
|
2014-02-11 02:04:44 +01:00
|
|
|
WriteBatch batch;
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Put(cf, k, v);
|
2014-02-11 02:04:44 +01:00
|
|
|
return Write(o, &batch);
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
}
|
|
|
|
using DB::Merge;
|
2014-02-11 02:04:44 +01:00
|
|
|
virtual Status Merge(const WriteOptions& o, ColumnFamilyHandle* cf,
|
2015-02-26 20:28:41 +01:00
|
|
|
const Slice& k, const Slice& v) override {
|
2014-02-11 02:04:44 +01:00
|
|
|
WriteBatch batch;
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Merge(cf, k, v);
|
2014-02-11 02:04:44 +01:00
|
|
|
return Write(o, &batch);
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
}
|
|
|
|
using DB::Delete;
|
2014-02-11 02:04:44 +01:00
|
|
|
virtual Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf,
|
2015-02-26 20:28:41 +01:00
|
|
|
const Slice& key) override {
|
2014-02-11 02:04:44 +01:00
|
|
|
WriteBatch batch;
|
2014-03-14 21:40:06 +01:00
|
|
|
batch.Delete(cf, key);
|
2014-02-11 02:04:44 +01:00
|
|
|
return Write(o, &batch);
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
}
|
|
|
|
using DB::Get;
|
2014-02-11 02:04:44 +01:00
|
|
|
virtual Status Get(const ReadOptions& options, ColumnFamilyHandle* cf,
|
2015-02-26 20:28:41 +01:00
|
|
|
const Slice& key, std::string* value) override {
|
2013-06-05 20:22:38 +02:00
|
|
|
return Status::NotSupported(key);
|
|
|
|
}
|
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::MultiGet;
|
|
|
|
virtual std::vector<Status> MultiGet(
|
|
|
|
const ReadOptions& options,
|
2014-02-11 02:04:44 +01:00
|
|
|
const std::vector<ColumnFamilyHandle*>& column_family,
|
2015-02-26 20:28:41 +01:00
|
|
|
const std::vector<Slice>& keys,
|
|
|
|
std::vector<std::string>* values) override {
|
2013-06-05 20:22:38 +02:00
|
|
|
std::vector<Status> s(keys.size(),
|
|
|
|
Status::NotSupported("Not implemented."));
|
|
|
|
return s;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2014-02-15 01:46:03 +01:00
|
|
|
|
2014-02-15 02:02:10 +01:00
|
|
|
using DB::GetPropertiesOfAllTables;
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status GetPropertiesOfAllTables(
|
|
|
|
ColumnFamilyHandle* column_family,
|
|
|
|
TablePropertiesCollection* props) override {
|
2014-02-14 01:28:21 +01:00
|
|
|
return Status();
|
|
|
|
}
|
2014-02-15 01:46:03 +01:00
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::KeyMayExist;
|
2013-07-26 21:57:01 +02:00
|
|
|
virtual bool KeyMayExist(const ReadOptions& options,
|
2014-02-11 02:04:44 +01:00
|
|
|
ColumnFamilyHandle* column_family, const Slice& key,
|
2015-02-26 20:28:41 +01:00
|
|
|
std::string* value,
|
|
|
|
bool* value_found = nullptr) override {
|
2013-07-26 21:57:01 +02:00
|
|
|
if (value_found != nullptr) {
|
|
|
|
*value_found = false;
|
|
|
|
}
|
2013-07-06 03:49:18 +02:00
|
|
|
return true; // Not Supported directly
|
|
|
|
}
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::NewIterator;
|
|
|
|
virtual Iterator* NewIterator(const ReadOptions& options,
|
2015-02-26 20:28:41 +01:00
|
|
|
ColumnFamilyHandle* column_family) override {
|
2013-03-01 03:04:58 +01:00
|
|
|
if (options.snapshot == nullptr) {
|
2011-03-18 23:37:00 +01:00
|
|
|
KVMap* saved = new KVMap;
|
|
|
|
*saved = map_;
|
|
|
|
return new ModelIter(saved, true);
|
|
|
|
} else {
|
|
|
|
const KVMap* snapshot_state =
|
2011-05-21 04:17:43 +02:00
|
|
|
&(reinterpret_cast<const ModelSnapshot*>(options.snapshot)->map_);
|
2011-03-18 23:37:00 +01:00
|
|
|
return new ModelIter(snapshot_state, false);
|
|
|
|
}
|
|
|
|
}
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
virtual Status NewIterators(
|
|
|
|
const ReadOptions& options,
|
2014-02-11 02:04:44 +01:00
|
|
|
const std::vector<ColumnFamilyHandle*>& column_family,
|
2015-02-26 20:28:41 +01:00
|
|
|
std::vector<Iterator*>* iterators) override {
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
return Status::NotSupported("Not supported yet");
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual const Snapshot* GetSnapshot() override {
|
2011-05-21 04:17:43 +02:00
|
|
|
ModelSnapshot* snapshot = new ModelSnapshot;
|
|
|
|
snapshot->map_ = map_;
|
|
|
|
return snapshot;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void ReleaseSnapshot(const Snapshot* snapshot) override {
|
2011-05-21 04:17:43 +02:00
|
|
|
delete reinterpret_cast<const ModelSnapshot*>(snapshot);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status Write(const WriteOptions& options,
|
|
|
|
WriteBatch* batch) override {
|
2011-05-21 04:17:43 +02:00
|
|
|
class Handler : public WriteBatch::Handler {
|
|
|
|
public:
|
|
|
|
KVMap* map_;
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void Put(const Slice& key, const Slice& value) override {
|
2011-05-21 04:17:43 +02:00
|
|
|
(*map_)[key.ToString()] = value.ToString();
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void Merge(const Slice& key, const Slice& value) override {
|
2013-03-21 23:59:47 +01:00
|
|
|
// ignore merge for now
|
|
|
|
//(*map_)[key.ToString()] = value.ToString();
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void Delete(const Slice& key) override {
|
2011-05-21 04:17:43 +02:00
|
|
|
map_->erase(key.ToString());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Handler handler;
|
|
|
|
handler.map_ = &map_;
|
|
|
|
return batch->Iterate(&handler);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::GetProperty;
|
2014-02-11 02:04:44 +01:00
|
|
|
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
2015-02-26 20:28:41 +01:00
|
|
|
const Slice& property, std::string* value) override {
|
2011-03-18 23:37:00 +01:00
|
|
|
return false;
|
|
|
|
}
|
2014-07-29 00:28:53 +02:00
|
|
|
using DB::GetIntProperty;
|
|
|
|
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
|
|
|
const Slice& property, uint64_t* value) override {
|
|
|
|
return false;
|
|
|
|
}
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::GetApproximateSizes;
|
2014-02-11 02:04:44 +01:00
|
|
|
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
2015-06-13 03:04:30 +02:00
|
|
|
const Range* range, int n, uint64_t* sizes,
|
|
|
|
bool include_memtable) override {
|
2011-03-18 23:37:00 +01:00
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
sizes[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::CompactRange;
|
2015-06-17 23:36:14 +02:00
|
|
|
virtual Status CompactRange(const CompactRangeOptions& options,
|
|
|
|
ColumnFamilyHandle* column_family,
|
|
|
|
const Slice* start, const Slice* end) override {
|
2014-01-22 21:46:24 +01:00
|
|
|
return Status::NotSupported("Not supported operation.");
|
2011-10-06 01:30:28 +02:00
|
|
|
}
|
|
|
|
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
using DB::CompactFiles;
|
|
|
|
virtual Status CompactFiles(
|
|
|
|
const CompactionOptions& compact_options,
|
|
|
|
ColumnFamilyHandle* column_family,
|
|
|
|
const std::vector<std::string>& input_file_names,
|
|
|
|
const int output_level, const int output_path_id = -1) override {
|
|
|
|
return Status::NotSupported("Not supported operation.");
|
|
|
|
}
|
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::NumberLevels;
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual int NumberLevels(ColumnFamilyHandle* column_family) override {
|
|
|
|
return 1;
|
|
|
|
}
|
2012-06-23 04:30:03 +02:00
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::MaxMemCompactionLevel;
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual int MaxMemCompactionLevel(
|
|
|
|
ColumnFamilyHandle* column_family) override {
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
return 1;
|
2012-06-23 04:30:03 +02:00
|
|
|
}
|
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::Level0StopWriteTrigger;
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual int Level0StopWriteTrigger(
|
|
|
|
ColumnFamilyHandle* column_family) override {
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
return -1;
|
2012-06-23 04:30:03 +02:00
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual const std::string& GetName() const override { return name_; }
|
[RocksDB] BackupableDB
Summary:
In this diff I present you BackupableDB v1. You can easily use it to backup your DB and it will do incremental snapshots for you.
Let's first describe how you would use BackupableDB. It's inheriting StackableDB interface so you can easily construct it with your DB object -- it will add a method RollTheSnapshot() to the DB object. When you call RollTheSnapshot(), current snapshot of the DB will be stored in the backup dir. To restore, you can just call RestoreDBFromBackup() on a BackupableDB (which is a static method) and it will restore all files from the backup dir. In the next version, it will even support automatic backuping every X minutes.
There are multiple things you can configure:
1. backup_env and db_env can be different, which is awesome because then you can easily backup to HDFS or wherever you feel like.
2. sync - if true, it *guarantees* backup consistency on machine reboot
3. number of snapshots to keep - this will keep last N snapshots around if you want, for some reason, be able to restore from an earlier snapshot. All the backuping is done in incremental fashion - if we already have 00010.sst, we will not copy it again. *IMPORTANT* -- This is based on assumption that 00010.sst never changes - two files named 00010.sst from the same DB will always be exactly the same. Is this true? I always copy manifest, current and log files.
4. You can decide if you want to flush the memtables before you backup, or you're fine with backing up the log files -- either way, you get a complete and consistent view of the database at a time of backup.
5. More things you can find in BackupableDBOptions
Here is the directory structure I use:
backup_dir/CURRENT_SNAPSHOT - just 4 bytes holding the latest snapshot
0, 1, 2, ... - files containing serialized version of each snapshot - containing a list of files
files/*.sst - sst files shared between snapshots - if one snapshot references 00010.sst and another one needs to backup it from the DB, it will just reference the same file
files/ 0/, 1/, 2/, ... - snapshot directories containing private snapshot files - current, manifest and log files
All the files are ref counted and deleted immediatelly when they get out of scope.
Some other stuff in this diff:
1. Added GetEnv() method to the DB. Discussed with @haobo and we agreed that it seems right thing to do.
2. Fixed StackableDB interface. The way it was set up before, I was not able to implement BackupableDB.
Test Plan:
I have a unittest, but please don't look at this yet. I just hacked it up to help me with debugging. I will write a lot of good tests and update the diff.
Also, `make asan_check`
Reviewers: dhruba, haobo, emayanke
Reviewed By: dhruba
CC: leveldb, haobo
Differential Revision: https://reviews.facebook.net/D14295
2013-12-09 23:06:52 +01:00
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Env* GetEnv() const override { return nullptr; }
|
2013-11-25 21:39:23 +01:00
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::GetOptions;
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual const Options& GetOptions(
|
|
|
|
ColumnFamilyHandle* column_family) const override {
|
2013-11-26 00:51:50 +01:00
|
|
|
return options_;
|
|
|
|
}
|
|
|
|
|
2015-05-11 23:51:51 +02:00
|
|
|
using DB::GetDBOptions;
|
|
|
|
virtual const DBOptions& GetDBOptions() const override { return options_; }
|
|
|
|
|
[RocksDB] [Column Family] Interface proposal
Summary:
<This diff is for Column Family branch>
Sharing some of the work I've done so far. This diff compiles and passes the tests.
The biggest change is in options.h - I broke down Options into two parts - DBOptions and ColumnFamilyOptions. DBOptions is DB-specific (env, create_if_missing, block_cache, etc.) and ColumnFamilyOptions is column family-specific (all compaction options, compresion options, etc.). Note that this does not break backwards compatibility at all.
Further, I created DBWithColumnFamily which inherits DB interface and adds new functions with column family support. Clients can transparently switch to DBWithColumnFamily and it will not break their backwards compatibility.
There are few methods worth checking out: ListColumnFamilies(), MultiNewIterator(), MultiGet() and GetSnapshot(). [GetSnapshot() returns the snapshot across all column families for now - I think that's what we agreed on]
Finally, I made small changes to WriteBatch so we are able to atomically insert data across column families.
Please provide feedback.
Test Plan: make check works, the code is backward compatible
Reviewers: dhruba, haobo, sdong, kailiu, emayanke
CC: leveldb
Differential Revision: https://reviews.facebook.net/D14445
2013-12-03 20:14:09 +01:00
|
|
|
using DB::Flush;
|
|
|
|
virtual Status Flush(const rocksdb::FlushOptions& options,
|
2015-02-26 20:28:41 +01:00
|
|
|
ColumnFamilyHandle* column_family) override {
|
2012-07-06 20:42:09 +02:00
|
|
|
Status ret;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status DisableFileDeletions() override { return Status::OK(); }
|
|
|
|
virtual Status EnableFileDeletions(bool force) override {
|
2012-09-15 02:11:35 +02:00
|
|
|
return Status::OK();
|
|
|
|
}
|
2013-10-03 23:38:32 +02:00
|
|
|
virtual Status GetLiveFiles(std::vector<std::string>&, uint64_t* size,
|
2015-02-26 20:28:41 +01:00
|
|
|
bool flush_memtable = true) override {
|
2012-09-15 02:11:35 +02:00
|
|
|
return Status::OK();
|
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status GetSortedWalFiles(VectorLogPtr& files) override {
|
2013-08-06 21:54:37 +02:00
|
|
|
return Status::OK();
|
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Status DeleteFile(std::string name) override { return Status::OK(); }
|
2013-08-06 21:54:37 +02:00
|
|
|
|
2015-05-21 20:01:48 +02:00
|
|
|
virtual Status GetDbIdentity(std::string& identity) const override {
|
2013-12-05 19:16:39 +01:00
|
|
|
return Status::OK();
|
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual SequenceNumber GetLatestSequenceNumber() const override { return 0; }
|
2014-02-28 20:50:36 +01:00
|
|
|
virtual Status GetUpdatesSince(
|
|
|
|
rocksdb::SequenceNumber, unique_ptr<rocksdb::TransactionLogIterator>*,
|
|
|
|
const TransactionLogIterator::ReadOptions&
|
2015-02-26 20:28:41 +01:00
|
|
|
read_options = TransactionLogIterator::ReadOptions()) override {
|
2012-11-30 02:28:37 +01:00
|
|
|
return Status::NotSupported("Not supported in Model DB");
|
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual ColumnFamilyHandle* DefaultColumnFamily() const override {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2014-02-11 02:04:44 +01:00
|
|
|
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
virtual void GetColumnFamilyMetaData(
|
|
|
|
ColumnFamilyHandle* column_family,
|
2015-02-26 20:28:41 +01:00
|
|
|
ColumnFamilyMetaData* metadata) override {}
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
private:
|
|
|
|
class ModelIter: public Iterator {
|
|
|
|
public:
|
|
|
|
ModelIter(const KVMap* map, bool owned)
|
|
|
|
: map_(map), owned_(owned), iter_(map_->end()) {
|
|
|
|
}
|
|
|
|
~ModelIter() {
|
|
|
|
if (owned_) delete map_;
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual bool Valid() const override { return iter_ != map_->end(); }
|
|
|
|
virtual void SeekToFirst() override { iter_ = map_->begin(); }
|
|
|
|
virtual void SeekToLast() override {
|
2011-03-18 23:37:00 +01:00
|
|
|
if (map_->empty()) {
|
|
|
|
iter_ = map_->end();
|
|
|
|
} else {
|
|
|
|
iter_ = map_->find(map_->rbegin()->first);
|
|
|
|
}
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void Seek(const Slice& k) override {
|
2011-03-18 23:37:00 +01:00
|
|
|
iter_ = map_->lower_bound(k.ToString());
|
|
|
|
}
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual void Next() override { ++iter_; }
|
|
|
|
virtual void Prev() override {
|
2014-08-08 18:44:14 +02:00
|
|
|
if (iter_ == map_->begin()) {
|
|
|
|
iter_ = map_->end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
--iter_;
|
|
|
|
}
|
|
|
|
|
2015-02-26 20:28:41 +01:00
|
|
|
virtual Slice key() const override { return iter_->first; }
|
|
|
|
virtual Slice value() const override { return iter_->second; }
|
|
|
|
virtual Status status() const override { return Status::OK(); }
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
private:
|
|
|
|
const KVMap* const map_;
|
|
|
|
const bool owned_; // Do we own map_
|
|
|
|
KVMap::const_iterator iter_;
|
|
|
|
};
|
|
|
|
const Options options_;
|
|
|
|
KVMap map_;
|
[RocksDB] BackupableDB
Summary:
In this diff I present you BackupableDB v1. You can easily use it to backup your DB and it will do incremental snapshots for you.
Let's first describe how you would use BackupableDB. It's inheriting StackableDB interface so you can easily construct it with your DB object -- it will add a method RollTheSnapshot() to the DB object. When you call RollTheSnapshot(), current snapshot of the DB will be stored in the backup dir. To restore, you can just call RestoreDBFromBackup() on a BackupableDB (which is a static method) and it will restore all files from the backup dir. In the next version, it will even support automatic backuping every X minutes.
There are multiple things you can configure:
1. backup_env and db_env can be different, which is awesome because then you can easily backup to HDFS or wherever you feel like.
2. sync - if true, it *guarantees* backup consistency on machine reboot
3. number of snapshots to keep - this will keep last N snapshots around if you want, for some reason, be able to restore from an earlier snapshot. All the backuping is done in incremental fashion - if we already have 00010.sst, we will not copy it again. *IMPORTANT* -- This is based on assumption that 00010.sst never changes - two files named 00010.sst from the same DB will always be exactly the same. Is this true? I always copy manifest, current and log files.
4. You can decide if you want to flush the memtables before you backup, or you're fine with backing up the log files -- either way, you get a complete and consistent view of the database at a time of backup.
5. More things you can find in BackupableDBOptions
Here is the directory structure I use:
backup_dir/CURRENT_SNAPSHOT - just 4 bytes holding the latest snapshot
0, 1, 2, ... - files containing serialized version of each snapshot - containing a list of files
files/*.sst - sst files shared between snapshots - if one snapshot references 00010.sst and another one needs to backup it from the DB, it will just reference the same file
files/ 0/, 1/, 2/, ... - snapshot directories containing private snapshot files - current, manifest and log files
All the files are ref counted and deleted immediatelly when they get out of scope.
Some other stuff in this diff:
1. Added GetEnv() method to the DB. Discussed with @haobo and we agreed that it seems right thing to do.
2. Fixed StackableDB interface. The way it was set up before, I was not able to implement BackupableDB.
Test Plan:
I have a unittest, but please don't look at this yet. I just hacked it up to help me with debugging. I will write a lot of good tests and update the diff.
Also, `make asan_check`
Reviewers: dhruba, haobo, emayanke
Reviewed By: dhruba
CC: leveldb, haobo
Differential Revision: https://reviews.facebook.net/D14295
2013-12-09 23:06:52 +01:00
|
|
|
std::string name_ = "";
|
2011-03-18 23:37:00 +01:00
|
|
|
};
|
|
|
|
|
2013-08-23 08:10:02 +02:00
|
|
|
static std::string RandomKey(Random* rnd, int minimum = 0) {
|
|
|
|
int len;
|
|
|
|
do {
|
|
|
|
len = (rnd->OneIn(3)
|
|
|
|
? 1 // Short sometimes to encourage collisions
|
|
|
|
: (rnd->OneIn(100) ? rnd->Skewed(10) : rnd->Uniform(10)));
|
|
|
|
} while (len < minimum);
|
2011-03-18 23:37:00 +01:00
|
|
|
return test::RandomKey(rnd, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool CompareIterators(int step,
|
|
|
|
DB* model,
|
|
|
|
DB* db,
|
|
|
|
const Snapshot* model_snap,
|
|
|
|
const Snapshot* db_snap) {
|
|
|
|
ReadOptions options;
|
|
|
|
options.snapshot = model_snap;
|
|
|
|
Iterator* miter = model->NewIterator(options);
|
|
|
|
options.snapshot = db_snap;
|
|
|
|
Iterator* dbiter = db->NewIterator(options);
|
|
|
|
bool ok = true;
|
|
|
|
int count = 0;
|
|
|
|
for (miter->SeekToFirst(), dbiter->SeekToFirst();
|
|
|
|
ok && miter->Valid() && dbiter->Valid();
|
|
|
|
miter->Next(), dbiter->Next()) {
|
|
|
|
count++;
|
|
|
|
if (miter->key().compare(dbiter->key()) != 0) {
|
|
|
|
fprintf(stderr, "step %d: Key mismatch: '%s' vs. '%s'\n",
|
|
|
|
step,
|
|
|
|
EscapeString(miter->key()).c_str(),
|
|
|
|
EscapeString(dbiter->key()).c_str());
|
|
|
|
ok = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (miter->value().compare(dbiter->value()) != 0) {
|
|
|
|
fprintf(stderr, "step %d: Value mismatch for key '%s': '%s' vs. '%s'\n",
|
|
|
|
step,
|
|
|
|
EscapeString(miter->key()).c_str(),
|
|
|
|
EscapeString(miter->value()).c_str(),
|
|
|
|
EscapeString(miter->value()).c_str());
|
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
if (miter->Valid() != dbiter->Valid()) {
|
|
|
|
fprintf(stderr, "step %d: Mismatch at end of iterators: %d vs. %d\n",
|
|
|
|
step, miter->Valid(), dbiter->Valid());
|
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete miter;
|
|
|
|
delete dbiter;
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, Randomized) {
|
2015-02-03 21:19:56 +01:00
|
|
|
anon::OptionsOverride options_override;
|
|
|
|
options_override.skip_policy = kSkipNoSnapshot;
|
2011-03-18 23:37:00 +01:00
|
|
|
Random rnd(test::RandomSeed());
|
2012-04-17 17:36:46 +02:00
|
|
|
do {
|
2015-02-03 21:19:56 +01:00
|
|
|
ModelDB model(CurrentOptions(options_override));
|
2012-04-17 17:36:46 +02:00
|
|
|
const int N = 10000;
|
2013-03-01 03:04:58 +01:00
|
|
|
const Snapshot* model_snap = nullptr;
|
|
|
|
const Snapshot* db_snap = nullptr;
|
2012-04-17 17:36:46 +02:00
|
|
|
std::string k, v;
|
|
|
|
for (int step = 0; step < N; step++) {
|
|
|
|
// TODO(sanjay): Test Get() works
|
|
|
|
int p = rnd.Uniform(100);
|
2013-08-23 08:10:02 +02:00
|
|
|
int minimum = 0;
|
2013-12-20 18:35:24 +01:00
|
|
|
if (option_config_ == kHashSkipList ||
|
2013-12-27 21:56:27 +01:00
|
|
|
option_config_ == kHashLinkList ||
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
option_config_ == kHashCuckoo ||
|
2014-04-10 23:19:43 +02:00
|
|
|
option_config_ == kPlainTableFirstBytePrefix ||
|
|
|
|
option_config_ == kBlockBasedTableWithWholeKeyHashIndex ||
|
|
|
|
option_config_ == kBlockBasedTableWithPrefixHashIndex) {
|
2013-08-23 08:10:02 +02:00
|
|
|
minimum = 1;
|
|
|
|
}
|
2012-04-17 17:36:46 +02:00
|
|
|
if (p < 45) { // Put
|
2013-08-23 08:10:02 +02:00
|
|
|
k = RandomKey(&rnd, minimum);
|
2012-04-17 17:36:46 +02:00
|
|
|
v = RandomString(&rnd,
|
|
|
|
rnd.OneIn(20)
|
|
|
|
? 100 + rnd.Uniform(100)
|
|
|
|
: rnd.Uniform(8));
|
|
|
|
ASSERT_OK(model.Put(WriteOptions(), k, v));
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), k, v));
|
|
|
|
|
|
|
|
} else if (p < 90) { // Delete
|
2013-08-23 08:10:02 +02:00
|
|
|
k = RandomKey(&rnd, minimum);
|
2012-04-17 17:36:46 +02:00
|
|
|
ASSERT_OK(model.Delete(WriteOptions(), k));
|
|
|
|
ASSERT_OK(db_->Delete(WriteOptions(), k));
|
|
|
|
|
|
|
|
|
|
|
|
} else { // Multi-element batch
|
|
|
|
WriteBatch b;
|
|
|
|
const int num = rnd.Uniform(8);
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
|
|
if (i == 0 || !rnd.OneIn(10)) {
|
2013-08-23 08:10:02 +02:00
|
|
|
k = RandomKey(&rnd, minimum);
|
2012-04-17 17:36:46 +02:00
|
|
|
} else {
|
|
|
|
// Periodically re-use the same key from the previous iter, so
|
|
|
|
// we have multiple entries in the write batch for the same key
|
|
|
|
}
|
|
|
|
if (rnd.OneIn(2)) {
|
|
|
|
v = RandomString(&rnd, rnd.Uniform(10));
|
|
|
|
b.Put(k, v);
|
|
|
|
} else {
|
|
|
|
b.Delete(k);
|
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2012-04-17 17:36:46 +02:00
|
|
|
ASSERT_OK(model.Write(WriteOptions(), &b));
|
|
|
|
ASSERT_OK(db_->Write(WriteOptions(), &b));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
if ((step % 100) == 0) {
|
2014-08-08 18:44:14 +02:00
|
|
|
// For DB instances that use the hash index + block-based table, the
|
|
|
|
// iterator will be invalid right when seeking a non-existent key, right
|
|
|
|
// than return a key that is close to it.
|
|
|
|
if (option_config_ != kBlockBasedTableWithWholeKeyHashIndex &&
|
|
|
|
option_config_ != kBlockBasedTableWithPrefixHashIndex) {
|
|
|
|
ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr));
|
|
|
|
ASSERT_TRUE(CompareIterators(step, &model, db_, model_snap, db_snap));
|
|
|
|
}
|
2014-04-10 23:19:43 +02:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
// Save a snapshot from each DB this time that we'll use next
|
|
|
|
// time we compare things, to make sure the current state is
|
|
|
|
// preserved with the snapshot
|
2013-03-01 03:04:58 +01:00
|
|
|
if (model_snap != nullptr) model.ReleaseSnapshot(model_snap);
|
|
|
|
if (db_snap != nullptr) db_->ReleaseSnapshot(db_snap);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
|
2015-02-03 21:19:56 +01:00
|
|
|
auto options = CurrentOptions(options_override);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2013-03-01 03:04:58 +01:00
|
|
|
ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr));
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
model_snap = model.GetSnapshot();
|
|
|
|
db_snap = db_->GetSnapshot();
|
|
|
|
}
|
2014-08-08 18:44:14 +02:00
|
|
|
|
|
|
|
if ((step % 2000) == 0) {
|
2015-01-28 06:00:33 +01:00
|
|
|
fprintf(stderr,
|
2014-08-08 18:44:14 +02:00
|
|
|
"DBTest.Randomized, option ID: %d, step: %d out of %d\n",
|
|
|
|
option_config_, step, N);
|
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2013-03-01 03:04:58 +01:00
|
|
|
if (model_snap != nullptr) model.ReleaseSnapshot(model_snap);
|
|
|
|
if (db_snap != nullptr) db_->ReleaseSnapshot(db_snap);
|
Add a new mem-table representation based on cuckoo hash.
Summary:
= Major Changes =
* Add a new mem-table representation, HashCuckooRep, which is based cuckoo hash.
Cuckoo hash uses multiple hash functions. This allows each key to have multiple
possible locations in the mem-table.
- Put: When insert a key, it will try to find whether one of its possible
locations is vacant and store the key. If none of its possible
locations are available, then it will kick out a victim key and
store at that location. The kicked-out victim key will then be
stored at a vacant space of its possible locations or kick-out
another victim. In this diff, the kick-out path (known as
cuckoo-path) is found using BFS, which guarantees to be the shortest.
- Get: Simply tries all possible locations of a key --- this guarantees
worst-case constant time complexity.
- Time complexity: O(1) for Get, and average O(1) for Put if the
fullness of the mem-table is below 80%.
- Default using two hash functions, the number of hash functions used
by the cuckoo-hash may dynamically increase if it fails to find a
short-enough kick-out path.
- Currently, HashCuckooRep does not support iteration and snapshots,
as our current main purpose of this is to optimize point access.
= Minor Changes =
* Add IsSnapshotSupported() to DB to indicate whether the current DB
supports snapshots. If it returns false, then DB::GetSnapshot() will
always return nullptr.
Test Plan:
Run existing tests. Will develop a test specifically for cuckoo hash in
the next diff.
Reviewers: sdong, haobo
Reviewed By: sdong
CC: leveldb, dhruba, igor
Differential Revision: https://reviews.facebook.net/D16155
2014-04-30 02:13:46 +02:00
|
|
|
// skip cuckoo hash as it does not support snapshot.
|
2014-08-08 18:44:14 +02:00
|
|
|
} while (ChangeOptions(kSkipDeletesFilterFirst | kSkipNoSeekToLast |
|
|
|
|
kSkipHashCuckoo));
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, MultiGetSimple) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "k1", "v1"));
|
|
|
|
ASSERT_OK(Put(1, "k2", "v2"));
|
|
|
|
ASSERT_OK(Put(1, "k3", "v3"));
|
|
|
|
ASSERT_OK(Put(1, "k4", "v4"));
|
|
|
|
ASSERT_OK(Delete(1, "k4"));
|
|
|
|
ASSERT_OK(Put(1, "k5", "v5"));
|
|
|
|
ASSERT_OK(Delete(1, "no_key"));
|
|
|
|
|
|
|
|
std::vector<Slice> keys({"k1", "k2", "k3", "k4", "k5", "no_key"});
|
|
|
|
|
|
|
|
std::vector<std::string> values(20, "Temporary data to be overwritten");
|
|
|
|
std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
|
|
|
|
|
|
|
|
std::vector<Status> s = db_->MultiGet(ReadOptions(), cfs, keys, &values);
|
|
|
|
ASSERT_EQ(values.size(), keys.size());
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_EQ(values[0], "v1");
|
|
|
|
ASSERT_EQ(values[1], "v2");
|
|
|
|
ASSERT_EQ(values[2], "v3");
|
|
|
|
ASSERT_EQ(values[4], "v5");
|
|
|
|
|
|
|
|
ASSERT_OK(s[0]);
|
|
|
|
ASSERT_OK(s[1]);
|
|
|
|
ASSERT_OK(s[2]);
|
|
|
|
ASSERT_TRUE(s[3].IsNotFound());
|
|
|
|
ASSERT_OK(s[4]);
|
|
|
|
ASSERT_TRUE(s[5].IsNotFound());
|
|
|
|
} while (ChangeCompactOptions());
|
2013-06-05 20:22:38 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, MultiGetEmpty) {
|
2013-08-08 00:20:41 +02:00
|
|
|
do {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2013-08-08 00:20:41 +02:00
|
|
|
// Empty Key Set
|
|
|
|
std::vector<Slice> keys;
|
|
|
|
std::vector<std::string> values;
|
2014-02-07 23:47:16 +01:00
|
|
|
std::vector<ColumnFamilyHandle*> cfs;
|
|
|
|
std::vector<Status> s = db_->MultiGet(ReadOptions(), cfs, keys, &values);
|
|
|
|
ASSERT_EQ(s.size(), 0U);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Empty Database, Empty Key Set
|
2014-10-29 19:59:18 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-02-07 23:47:16 +01:00
|
|
|
s = db_->MultiGet(ReadOptions(), cfs, keys, &values);
|
|
|
|
ASSERT_EQ(s.size(), 0U);
|
2013-08-08 00:20:41 +02:00
|
|
|
|
|
|
|
// Empty Database, Search for Keys
|
|
|
|
keys.resize(2);
|
|
|
|
keys[0] = "a";
|
|
|
|
keys[1] = "b";
|
2014-02-07 23:47:16 +01:00
|
|
|
cfs.push_back(handles_[0]);
|
|
|
|
cfs.push_back(handles_[1]);
|
|
|
|
s = db_->MultiGet(ReadOptions(), cfs, keys, &values);
|
2013-08-08 00:20:41 +02:00
|
|
|
ASSERT_EQ((int)s.size(), 2);
|
|
|
|
ASSERT_TRUE(s[0].IsNotFound() && s[1].IsNotFound());
|
|
|
|
} while (ChangeCompactOptions());
|
2013-06-05 20:22:38 +02:00
|
|
|
}
|
|
|
|
|
2014-04-10 06:17:14 +02:00
|
|
|
namespace {
|
2013-08-13 23:04:56 +02:00
|
|
|
void PrefixScanInit(DBTest *dbtest) {
|
|
|
|
char buf[100];
|
|
|
|
std::string keystr;
|
|
|
|
const int small_range_sstfiles = 5;
|
|
|
|
const int big_range_sstfiles = 5;
|
|
|
|
|
|
|
|
// Generate 11 sst files with the following prefix ranges.
|
|
|
|
// GROUP 0: [0,10] (level 1)
|
|
|
|
// GROUP 1: [1,2], [2,3], [3,4], [4,5], [5, 6] (level 0)
|
|
|
|
// GROUP 2: [0,6], [0,7], [0,8], [0,9], [0,10] (level 0)
|
|
|
|
//
|
|
|
|
// A seek with the previous API would do 11 random I/Os (to all the
|
|
|
|
// files). With the new API and a prefix filter enabled, we should
|
|
|
|
// only do 2 random I/O, to the 2 files containing the key.
|
|
|
|
|
|
|
|
// GROUP 0
|
|
|
|
snprintf(buf, sizeof(buf), "%02d______:start", 0);
|
|
|
|
keystr = std::string(buf);
|
|
|
|
ASSERT_OK(dbtest->Put(keystr, keystr));
|
|
|
|
snprintf(buf, sizeof(buf), "%02d______:end", 10);
|
|
|
|
keystr = std::string(buf);
|
|
|
|
ASSERT_OK(dbtest->Put(keystr, keystr));
|
2014-02-07 23:47:16 +01:00
|
|
|
dbtest->Flush();
|
2015-06-17 23:36:14 +02:00
|
|
|
dbtest->dbfull()->CompactRange(CompactRangeOptions(), nullptr,
|
|
|
|
nullptr); // move to level 1
|
2013-08-13 23:04:56 +02:00
|
|
|
|
|
|
|
// GROUP 1
|
|
|
|
for (int i = 1; i <= small_range_sstfiles; i++) {
|
|
|
|
snprintf(buf, sizeof(buf), "%02d______:start", i);
|
|
|
|
keystr = std::string(buf);
|
|
|
|
ASSERT_OK(dbtest->Put(keystr, keystr));
|
|
|
|
snprintf(buf, sizeof(buf), "%02d______:end", i+1);
|
|
|
|
keystr = std::string(buf);
|
|
|
|
ASSERT_OK(dbtest->Put(keystr, keystr));
|
2014-02-07 23:47:16 +01:00
|
|
|
dbtest->Flush();
|
2013-08-13 23:04:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GROUP 2
|
|
|
|
for (int i = 1; i <= big_range_sstfiles; i++) {
|
|
|
|
snprintf(buf, sizeof(buf), "%02d______:start", 0);
|
|
|
|
keystr = std::string(buf);
|
|
|
|
ASSERT_OK(dbtest->Put(keystr, keystr));
|
|
|
|
snprintf(buf, sizeof(buf), "%02d______:end",
|
|
|
|
small_range_sstfiles+i+1);
|
|
|
|
keystr = std::string(buf);
|
|
|
|
ASSERT_OK(dbtest->Put(keystr, keystr));
|
2014-02-07 23:47:16 +01:00
|
|
|
dbtest->Flush();
|
2013-08-13 23:04:56 +02:00
|
|
|
}
|
|
|
|
}
|
2014-04-10 06:17:14 +02:00
|
|
|
} // namespace
|
2013-08-13 23:04:56 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PrefixScan) {
|
2015-02-18 20:49:31 +01:00
|
|
|
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip,
|
|
|
|
kSkipNoPrefix);
|
2014-09-08 19:37:05 +02:00
|
|
|
while (ChangeFilterOptions()) {
|
|
|
|
int count;
|
|
|
|
Slice prefix;
|
|
|
|
Slice key;
|
|
|
|
char buf[100];
|
|
|
|
Iterator* iter;
|
|
|
|
snprintf(buf, sizeof(buf), "03______:");
|
|
|
|
prefix = Slice(buf, 8);
|
|
|
|
key = Slice(buf, 9);
|
|
|
|
// db configs
|
|
|
|
env_->count_random_reads_ = true;
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.max_background_compactions = 2;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
2013-08-13 23:04:56 +02:00
|
|
|
|
2014-09-08 19:37:05 +02:00
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.no_block_cache = true;
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
|
|
|
table_options.whole_key_filtering = false;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
2014-08-25 23:22:05 +02:00
|
|
|
|
2014-09-08 19:37:05 +02:00
|
|
|
// 11 RAND I/Os
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-09-08 19:37:05 +02:00
|
|
|
PrefixScanInit(this);
|
|
|
|
count = 0;
|
|
|
|
env_->random_read_counter_.Reset();
|
|
|
|
iter = db_->NewIterator(ReadOptions());
|
|
|
|
for (iter->Seek(prefix); iter->Valid(); iter->Next()) {
|
|
|
|
if (! iter->key().starts_with(prefix)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
count++;
|
2013-08-13 23:04:56 +02:00
|
|
|
}
|
2014-09-08 19:37:05 +02:00
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
delete iter;
|
|
|
|
ASSERT_EQ(count, 2);
|
|
|
|
ASSERT_EQ(env_->random_read_counter_.Read(), 2);
|
|
|
|
Close();
|
|
|
|
} // end of while
|
2015-02-18 20:49:31 +01:00
|
|
|
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip, 0);
|
2013-08-13 23:04:56 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TailingIteratorSingle) {
|
2014-01-17 06:56:26 +01:00
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
|
|
|
|
// add a record and check that iter can see it
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor"));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().ToString(), "mirko");
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TailingIteratorKeepAdding) {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-01-17 06:56:26 +01:00
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
2014-01-17 06:56:26 +01:00
|
|
|
std::string value(1024, 'a');
|
|
|
|
|
|
|
|
const int num_records = 10000;
|
|
|
|
for (int i = 0; i < num_records; ++i) {
|
|
|
|
char buf[32];
|
|
|
|
snprintf(buf, sizeof(buf), "%016d", i);
|
|
|
|
|
|
|
|
Slice key(buf, 16);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, key, value));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
|
|
|
iter->Seek(key);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TailingIteratorSeekToNext) {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-06-10 18:57:26 +02:00
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
|
|
std::string value(1024, 'a');
|
|
|
|
|
|
|
|
const int num_records = 1000;
|
|
|
|
for (int i = 1; i < num_records; ++i) {
|
|
|
|
char buf1[32];
|
|
|
|
char buf2[32];
|
|
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
|
|
|
|
|
|
Slice key(buf1, 20);
|
|
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
|
|
|
|
if (i % 100 == 99) {
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
|
|
Slice target(buf2, 20);
|
|
|
|
iter->Seek(target);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
|
|
}
|
|
|
|
for (int i = 2 * num_records; i > 0; --i) {
|
|
|
|
char buf1[32];
|
|
|
|
char buf2[32];
|
|
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
|
|
|
|
|
|
Slice key(buf1, 20);
|
|
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
|
|
|
|
if (i % 100 == 99) {
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
|
|
Slice target(buf2, 20);
|
|
|
|
iter->Seek(target);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TailingIteratorDeletes) {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-01-17 06:56:26 +01:00
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
|
|
|
// write a single record, read it using the iterator, then delete it
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "0test", "test"));
|
2014-01-17 06:56:26 +01:00
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().ToString(), "0test");
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Delete(1, "0test"));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
|
|
|
// write many more records
|
|
|
|
const int num_records = 10000;
|
|
|
|
std::string value(1024, 'A');
|
|
|
|
|
|
|
|
for (int i = 0; i < num_records; ++i) {
|
|
|
|
char buf[32];
|
|
|
|
snprintf(buf, sizeof(buf), "1%015d", i);
|
|
|
|
|
|
|
|
Slice key(buf, 16);
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, key, value));
|
2014-01-17 06:56:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// force a flush to make sure that no records are read from memtable
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
|
|
|
// skip "0test"
|
|
|
|
iter->Next();
|
|
|
|
|
|
|
|
// make sure we can read all new records using the existing iterator
|
|
|
|
int count = 0;
|
|
|
|
for (; iter->Valid(); iter->Next(), ++count) ;
|
|
|
|
|
|
|
|
ASSERT_EQ(count, num_records);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TailingIteratorPrefixSeek) {
|
2015-02-18 20:49:31 +01:00
|
|
|
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip,
|
|
|
|
kSkipNoPrefix);
|
2014-01-17 06:56:26 +01:00
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.disable_auto_compactions = true;
|
2014-03-10 20:56:46 +01:00
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
2014-07-01 00:54:31 +02:00
|
|
|
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-01-17 06:56:26 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
|
|
ASSERT_OK(Put(1, "0101", "test"));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Flush(1));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
2014-02-07 23:47:16 +01:00
|
|
|
ASSERT_OK(Put(1, "0202", "test"));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
|
|
|
// Seek(0102) shouldn't find any records since 0202 has a different prefix
|
|
|
|
iter->Seek("0102");
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
|
|
|
|
iter->Seek("0202");
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().ToString(), "0202");
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
2015-02-18 20:49:31 +01:00
|
|
|
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip, 0);
|
2014-01-17 06:56:26 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TailingIteratorIncomplete) {
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
2014-07-10 02:46:18 +02:00
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.read_tier = kBlockCacheTier;
|
|
|
|
|
|
|
|
std::string key("key");
|
|
|
|
std::string value("value");
|
|
|
|
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
// we either see the entry or it's not in cache
|
|
|
|
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
2014-07-10 02:46:18 +02:00
|
|
|
iter->SeekToFirst();
|
|
|
|
// should still be true after compaction
|
|
|
|
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TailingIteratorSeekToSame) {
|
ForwardIterator seek bugfix
Summary:
If `NeedToSeekImmutable()` returns false, `SeekInternal()` won't reset the
contents of `immutable_min_heap_`. However, since it calls `UpdateCurrent()`
unconditionally, if `current_` is one of immutable iterators (previously popped
from `immutable_min_heap_`), `UpdateCurrent()` will overwrite it. As a result,
if old `current_` in fact pointed to the smallest entry, forward iterator will
skip some records.
Fix implemented in this diff pushes `current_` back to `immutable_min_heap_`
before calling `UpdateCurrent()`.
Test Plan:
New unit test (courtesy of @lovro):
$ ROCKSDB_TESTS=TailingIteratorSeekToSame ./db_test
Reviewers: igor, dhruba, haobo, ljin
Reviewed By: ljin
Subscribers: lovro, leveldb
Differential Revision: https://reviews.facebook.net/D19653
2014-07-11 00:14:24 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.write_buffer_size = 1000;
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
ForwardIterator seek bugfix
Summary:
If `NeedToSeekImmutable()` returns false, `SeekInternal()` won't reset the
contents of `immutable_min_heap_`. However, since it calls `UpdateCurrent()`
unconditionally, if `current_` is one of immutable iterators (previously popped
from `immutable_min_heap_`), `UpdateCurrent()` will overwrite it. As a result,
if old `current_` in fact pointed to the smallest entry, forward iterator will
skip some records.
Fix implemented in this diff pushes `current_` back to `immutable_min_heap_`
before calling `UpdateCurrent()`.
Test Plan:
New unit test (courtesy of @lovro):
$ ROCKSDB_TESTS=TailingIteratorSeekToSame ./db_test
Reviewers: igor, dhruba, haobo, ljin
Reviewed By: ljin
Subscribers: lovro, leveldb
Differential Revision: https://reviews.facebook.net/D19653
2014-07-11 00:14:24 +02:00
|
|
|
|
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
|
|
|
|
const int NROWS = 10000;
|
|
|
|
// Write rows with keys 00000, 00002, 00004 etc.
|
|
|
|
for (int i = 0; i < NROWS; ++i) {
|
|
|
|
char buf[100];
|
|
|
|
snprintf(buf, sizeof(buf), "%05d", 2*i);
|
|
|
|
std::string key(buf);
|
|
|
|
std::string value("value");
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
|
|
// Seek to 00001. We expect to find 00002.
|
|
|
|
std::string start_key = "00001";
|
|
|
|
iter->Seek(start_key);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
|
|
|
|
std::string found = iter->key().ToString();
|
|
|
|
ASSERT_EQ("00002", found);
|
|
|
|
|
2015-02-18 20:49:31 +01:00
|
|
|
// Now seek to the same key. The iterator should remain in the same
|
|
|
|
// position.
|
|
|
|
iter->Seek(found);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(found, iter->key().ToString());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedTailingIteratorSingle) {
|
2015-02-18 20:49:31 +01:00
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.managed = true;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
|
|
|
|
// add a record and check that iter can see it
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor"));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().ToString(), "mirko");
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedTailingIteratorKeepAdding) {
|
2015-02-18 20:49:31 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.managed = true;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
|
|
std::string value(1024, 'a');
|
|
|
|
|
|
|
|
const int num_records = 10000;
|
|
|
|
for (int i = 0; i < num_records; ++i) {
|
|
|
|
char buf[32];
|
|
|
|
snprintf(buf, sizeof(buf), "%016d", i);
|
|
|
|
|
|
|
|
Slice key(buf, 16);
|
|
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
|
|
|
|
iter->Seek(key);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedTailingIteratorSeekToNext) {
|
2015-02-18 20:49:31 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.managed = true;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
|
|
std::string value(1024, 'a');
|
|
|
|
|
|
|
|
const int num_records = 1000;
|
|
|
|
for (int i = 1; i < num_records; ++i) {
|
|
|
|
char buf1[32];
|
|
|
|
char buf2[32];
|
|
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
|
|
|
|
|
|
Slice key(buf1, 20);
|
|
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
|
|
|
|
if (i % 100 == 99) {
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
|
|
Slice target(buf2, 20);
|
|
|
|
iter->Seek(target);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
|
|
}
|
|
|
|
for (int i = 2 * num_records; i > 0; --i) {
|
|
|
|
char buf1[32];
|
|
|
|
char buf2[32];
|
|
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
|
|
|
|
|
|
Slice key(buf1, 20);
|
|
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
|
|
|
|
if (i % 100 == 99) {
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
|
|
Slice target(buf2, 20);
|
|
|
|
iter->Seek(target);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedTailingIteratorDeletes) {
|
2015-02-18 20:49:31 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.managed = true;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
|
|
|
|
|
|
// write a single record, read it using the iterator, then delete it
|
|
|
|
ASSERT_OK(Put(1, "0test", "test"));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().ToString(), "0test");
|
|
|
|
ASSERT_OK(Delete(1, "0test"));
|
|
|
|
|
|
|
|
// write many more records
|
|
|
|
const int num_records = 10000;
|
|
|
|
std::string value(1024, 'A');
|
|
|
|
|
|
|
|
for (int i = 0; i < num_records; ++i) {
|
|
|
|
char buf[32];
|
|
|
|
snprintf(buf, sizeof(buf), "1%015d", i);
|
|
|
|
|
|
|
|
Slice key(buf, 16);
|
|
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
// force a flush to make sure that no records are read from memtable
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
|
|
|
|
// skip "0test"
|
|
|
|
iter->Next();
|
|
|
|
|
|
|
|
// make sure we can read all new records using the existing iterator
|
|
|
|
int count = 0;
|
|
|
|
for (; iter->Valid(); iter->Next(), ++count) {
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(count, num_records);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedTailingIteratorPrefixSeek) {
|
2015-02-18 20:49:31 +01:00
|
|
|
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip,
|
|
|
|
kSkipNoPrefix);
|
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.managed = true;
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
|
|
|
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
|
|
ASSERT_OK(Put(1, "0101", "test"));
|
|
|
|
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
|
|
|
|
ASSERT_OK(Put(1, "0202", "test"));
|
|
|
|
|
|
|
|
// Seek(0102) shouldn't find any records since 0202 has a different prefix
|
|
|
|
iter->Seek("0102");
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
|
|
|
|
iter->Seek("0202");
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().ToString(), "0202");
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip, 0);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedTailingIteratorIncomplete) {
|
2015-02-18 20:49:31 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.managed = true;
|
|
|
|
read_options.read_tier = kBlockCacheTier;
|
|
|
|
|
|
|
|
std::string key = "key";
|
|
|
|
std::string value = "value";
|
|
|
|
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
|
|
iter->SeekToFirst();
|
|
|
|
// we either see the entry or it's not in cache
|
|
|
|
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
2015-02-18 20:49:31 +01:00
|
|
|
iter->SeekToFirst();
|
|
|
|
// should still be true after compaction
|
|
|
|
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ManagedTailingIteratorSeekToSame) {
|
2015-02-18 20:49:31 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.write_buffer_size = 1000;
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
ReadOptions read_options;
|
|
|
|
read_options.tailing = true;
|
|
|
|
read_options.managed = true;
|
|
|
|
|
|
|
|
const int NROWS = 10000;
|
|
|
|
// Write rows with keys 00000, 00002, 00004 etc.
|
|
|
|
for (int i = 0; i < NROWS; ++i) {
|
|
|
|
char buf[100];
|
|
|
|
snprintf(buf, sizeof(buf), "%05d", 2 * i);
|
|
|
|
std::string key(buf);
|
|
|
|
std::string value("value");
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
|
|
// Seek to 00001. We expect to find 00002.
|
|
|
|
std::string start_key = "00001";
|
|
|
|
iter->Seek(start_key);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
|
|
|
|
std::string found = iter->key().ToString();
|
|
|
|
ASSERT_EQ("00002", found);
|
|
|
|
|
ForwardIterator seek bugfix
Summary:
If `NeedToSeekImmutable()` returns false, `SeekInternal()` won't reset the
contents of `immutable_min_heap_`. However, since it calls `UpdateCurrent()`
unconditionally, if `current_` is one of immutable iterators (previously popped
from `immutable_min_heap_`), `UpdateCurrent()` will overwrite it. As a result,
if old `current_` in fact pointed to the smallest entry, forward iterator will
skip some records.
Fix implemented in this diff pushes `current_` back to `immutable_min_heap_`
before calling `UpdateCurrent()`.
Test Plan:
New unit test (courtesy of @lovro):
$ ROCKSDB_TESTS=TailingIteratorSeekToSame ./db_test
Reviewers: igor, dhruba, haobo, ljin
Reviewed By: ljin
Subscribers: lovro, leveldb
Differential Revision: https://reviews.facebook.net/D19653
2014-07-11 00:14:24 +02:00
|
|
|
// Now seek to the same key. The iterator should remain in the same
|
|
|
|
// position.
|
|
|
|
iter->Seek(found);
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(found, iter->key().ToString());
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, BlockBasedTablePrefixIndexTest) {
|
2014-06-20 00:32:31 +02:00
|
|
|
// create a DB with block prefix index
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
table_options.index_type = BlockBasedTableOptions::kHashSearch;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
|
|
|
|
|
|
|
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-06-20 00:32:31 +02:00
|
|
|
ASSERT_OK(Put("k1", "v1"));
|
|
|
|
Flush();
|
|
|
|
ASSERT_OK(Put("k2", "v2"));
|
|
|
|
|
|
|
|
// Reopen it without prefix extractor, make sure everything still works.
|
|
|
|
// RocksDB should just fall back to the binary index.
|
|
|
|
table_options.index_type = BlockBasedTableOptions::kBinarySearch;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
options.prefix_extractor.reset();
|
|
|
|
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-06-20 00:32:31 +02:00
|
|
|
ASSERT_EQ("v1", Get("k1"));
|
|
|
|
ASSERT_EQ("v2", Get("k2"));
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ChecksumTest) {
|
2014-05-01 20:09:32 +02:00
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
|
|
|
|
table_options.checksum = kCRC32c;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-05-01 20:09:32 +02:00
|
|
|
ASSERT_OK(Put("a", "b"));
|
|
|
|
ASSERT_OK(Put("c", "d"));
|
|
|
|
ASSERT_OK(Flush()); // table with crc checksum
|
|
|
|
|
|
|
|
table_options.checksum = kxxHash;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-05-01 20:09:32 +02:00
|
|
|
ASSERT_OK(Put("e", "f"));
|
|
|
|
ASSERT_OK(Put("g", "h"));
|
|
|
|
ASSERT_OK(Flush()); // table with xxhash checksum
|
|
|
|
|
|
|
|
table_options.checksum = kCRC32c;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-05-01 20:09:32 +02:00
|
|
|
ASSERT_EQ("b", Get("a"));
|
|
|
|
ASSERT_EQ("d", Get("c"));
|
|
|
|
ASSERT_EQ("f", Get("e"));
|
|
|
|
ASSERT_EQ("h", Get("g"));
|
2014-01-17 06:56:26 +01:00
|
|
|
|
2014-05-01 20:09:32 +02:00
|
|
|
table_options.checksum = kCRC32c;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-05-01 20:09:32 +02:00
|
|
|
ASSERT_EQ("b", Get("a"));
|
|
|
|
ASSERT_EQ("d", Get("c"));
|
|
|
|
ASSERT_EQ("f", Get("e"));
|
|
|
|
ASSERT_EQ("h", Get("g"));
|
|
|
|
}
|
2014-05-21 20:43:35 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FIFOCompactionTest) {
|
2014-05-21 20:43:35 +02:00
|
|
|
for (int iter = 0; iter < 2; ++iter) {
|
|
|
|
// first iteration -- auto compaction
|
|
|
|
// second iteration -- manual compaction
|
|
|
|
Options options;
|
|
|
|
options.compaction_style = kCompactionStyleFIFO;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
if (iter == 1) {
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
}
|
2014-10-31 23:08:10 +01:00
|
|
|
options = CurrentOptions(options);
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-05-21 20:43:35 +02:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
|
|
for (int j = 0; j < 100; ++j) {
|
2014-11-25 05:44:49 +01:00
|
|
|
ASSERT_OK(Put(ToString(i * 100 + j), RandomString(&rnd, 1024)));
|
2014-05-21 20:43:35 +02:00
|
|
|
}
|
|
|
|
// flush should happen here
|
2015-04-13 20:39:45 +02:00
|
|
|
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
2014-05-21 20:43:35 +02:00
|
|
|
}
|
|
|
|
if (iter == 0) {
|
|
|
|
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
|
|
|
} else {
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
2014-05-21 20:43:35 +02:00
|
|
|
}
|
|
|
|
// only 5 files should survive
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 5);
|
|
|
|
for (int i = 0; i < 50; ++i) {
|
|
|
|
// these keys should be deleted in previous compaction
|
2014-11-25 05:44:49 +01:00
|
|
|
ASSERT_EQ("NOT_FOUND", Get(ToString(i)));
|
2014-05-21 20:43:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-04 00:47:02 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, SimpleWriteTimeoutTest) {
|
2014-09-09 20:50:05 +02:00
|
|
|
// Block compaction thread, which will also block the flushes because
|
|
|
|
// max_background_flushes == 0, so flushes are getting executed by the
|
|
|
|
// compaction thread
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
SleepingBackgroundTask sleeping_task_low;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
|
|
|
Env::Priority::LOW);
|
|
|
|
|
2014-07-04 00:47:02 +02:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = 100000;
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.max_total_wal_size = std::numeric_limits<uint64_t>::max();
|
2014-10-17 01:57:59 +02:00
|
|
|
WriteOptions write_opt;
|
2014-07-04 09:02:12 +02:00
|
|
|
write_opt.timeout_hint_us = 0;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-09-09 20:50:05 +02:00
|
|
|
// fill the two write buffers
|
2014-07-04 00:47:02 +02:00
|
|
|
ASSERT_OK(Put(Key(1), Key(1) + std::string(100000, 'v'), write_opt));
|
|
|
|
ASSERT_OK(Put(Key(2), Key(2) + std::string(100000, 'v'), write_opt));
|
|
|
|
// As the only two write buffers are full in this moment, the third
|
|
|
|
// Put is expected to be timed-out.
|
2014-08-26 18:38:45 +02:00
|
|
|
write_opt.timeout_hint_us = 50;
|
2014-07-04 00:47:02 +02:00
|
|
|
ASSERT_TRUE(
|
|
|
|
Put(Key(3), Key(3) + std::string(100000, 'v'), write_opt).IsTimedOut());
|
2014-09-09 20:50:05 +02:00
|
|
|
|
|
|
|
sleeping_task_low.WakeUp();
|
|
|
|
sleeping_task_low.WaitUntilDone();
|
2014-07-04 00:47:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Multi-threaded Timeout Test
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
static const int kValueSize = 1000;
|
|
|
|
static const int kWriteBufferSize = 100000;
|
|
|
|
|
|
|
|
struct TimeoutWriterState {
|
|
|
|
int id;
|
|
|
|
DB* db;
|
|
|
|
std::atomic<bool> done;
|
|
|
|
std::map<int, std::string> success_kvs;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void RandomTimeoutWriter(void* arg) {
|
|
|
|
TimeoutWriterState* state = reinterpret_cast<TimeoutWriterState*>(arg);
|
|
|
|
static const uint64_t kTimerBias = 50;
|
|
|
|
int thread_id = state->id;
|
|
|
|
DB* db = state->db;
|
|
|
|
|
|
|
|
Random rnd(1000 + thread_id);
|
2014-10-17 01:57:59 +02:00
|
|
|
WriteOptions write_opt;
|
2014-07-04 00:47:02 +02:00
|
|
|
write_opt.timeout_hint_us = 500;
|
|
|
|
int timeout_count = 0;
|
|
|
|
int num_keys = kNumKeys * 5;
|
|
|
|
|
|
|
|
for (int k = 0; k < num_keys; ++k) {
|
|
|
|
int key = k + thread_id * num_keys;
|
|
|
|
std::string value = RandomString(&rnd, kValueSize);
|
|
|
|
// only the second-half is randomized
|
|
|
|
if (k > num_keys / 2) {
|
|
|
|
switch (rnd.Next() % 5) {
|
|
|
|
case 0:
|
|
|
|
write_opt.timeout_hint_us = 500 * thread_id;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
write_opt.timeout_hint_us = num_keys - k;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
write_opt.timeout_hint_us = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
write_opt.timeout_hint_us = 0;
|
|
|
|
state->success_kvs.insert({key, value});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t time_before_put = db->GetEnv()->NowMicros();
|
|
|
|
Status s = db->Put(write_opt, Key(key), value);
|
|
|
|
uint64_t put_duration = db->GetEnv()->NowMicros() - time_before_put;
|
|
|
|
if (write_opt.timeout_hint_us == 0 ||
|
|
|
|
put_duration + kTimerBias < write_opt.timeout_hint_us) {
|
|
|
|
ASSERT_OK(s);
|
|
|
|
}
|
|
|
|
if (s.IsTimedOut()) {
|
|
|
|
timeout_count++;
|
|
|
|
ASSERT_GT(put_duration + kTimerBias, write_opt.timeout_hint_us);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state->done = true;
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, MTRandomTimeoutTest) {
|
2014-07-04 00:47:02 +02:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.level0_slowdown_writes_trigger = 10;
|
|
|
|
options.level0_stop_writes_trigger = 20;
|
|
|
|
options.write_buffer_size = kWriteBufferSize;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-07-04 00:47:02 +02:00
|
|
|
|
|
|
|
TimeoutWriterState thread_states[kNumThreads];
|
|
|
|
for (int tid = 0; tid < kNumThreads; ++tid) {
|
|
|
|
thread_states[tid].id = tid;
|
|
|
|
thread_states[tid].db = db_;
|
|
|
|
thread_states[tid].done = false;
|
|
|
|
env_->StartThread(RandomTimeoutWriter, &thread_states[tid]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int tid = 0; tid < kNumThreads; ++tid) {
|
|
|
|
while (thread_states[tid].done == false) {
|
|
|
|
env_->SleepForMicroseconds(100000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
|
|
|
|
for (int tid = 0; tid < kNumThreads; ++tid) {
|
|
|
|
auto& success_kvs = thread_states[tid].success_kvs;
|
|
|
|
for (auto it = success_kvs.begin(); it != success_kvs.end(); ++it) {
|
|
|
|
ASSERT_EQ(Get(Key(it->first)), it->second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, Level0StopWritesTest) {
|
2014-09-09 00:23:58 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 4;
|
2014-11-04 20:07:36 +01:00
|
|
|
options.disable_auto_compactions = true;
|
2014-09-09 00:23:58 +02:00
|
|
|
options.max_mem_compaction_level = 0;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-09-09 00:23:58 +02:00
|
|
|
|
|
|
|
// create 4 level0 tables
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
Put("a", "b");
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
WriteOptions woptions;
|
|
|
|
woptions.timeout_hint_us = 30 * 1000; // 30 ms
|
|
|
|
Status s = Put("a", "b", woptions);
|
|
|
|
ASSERT_TRUE(s.IsTimedOut());
|
|
|
|
}
|
|
|
|
|
2014-07-04 00:47:02 +02:00
|
|
|
} // anonymous namespace
|
|
|
|
|
2014-07-10 07:46:15 +02:00
|
|
|
/*
|
|
|
|
* This test is not reliable enough as it heavily depends on disk behavior.
|
2014-07-26 00:17:06 +02:00
|
|
|
*/
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, RateLimitingTest) {
|
2014-07-08 21:31:49 +02:00
|
|
|
Options options = CurrentOptions();
|
2014-07-09 00:15:00 +02:00
|
|
|
options.write_buffer_size = 1 << 20; // 1MB
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.target_file_size_base = 1 << 20; // 1MB
|
|
|
|
options.max_bytes_for_level_base = 4 << 20; // 4MB
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
2014-07-08 21:31:49 +02:00
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.env = env_;
|
2014-07-09 00:15:00 +02:00
|
|
|
options.IncreaseParallelism(4);
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-07-08 21:31:49 +02:00
|
|
|
|
2014-07-09 00:15:00 +02:00
|
|
|
WriteOptions wo;
|
|
|
|
wo.disableWAL = true;
|
|
|
|
|
2014-07-08 21:31:49 +02:00
|
|
|
// # no rate limiting
|
|
|
|
Random rnd(301);
|
|
|
|
uint64_t start = env_->NowMicros();
|
2014-07-09 00:15:00 +02:00
|
|
|
// Write ~96M data
|
|
|
|
for (int64_t i = 0; i < (96 << 10); ++i) {
|
|
|
|
ASSERT_OK(Put(RandomString(&rnd, 32),
|
|
|
|
RandomString(&rnd, (1 << 10) + 1), wo));
|
2014-07-08 21:31:49 +02:00
|
|
|
}
|
|
|
|
uint64_t elapsed = env_->NowMicros() - start;
|
|
|
|
double raw_rate = env_->bytes_written_ * 1000000 / elapsed;
|
|
|
|
Close();
|
|
|
|
|
|
|
|
// # rate limiting with 0.7 x threshold
|
|
|
|
options.rate_limiter.reset(
|
2014-07-26 00:17:06 +02:00
|
|
|
NewGenericRateLimiter(static_cast<int64_t>(0.7 * raw_rate)));
|
2014-07-08 21:31:49 +02:00
|
|
|
env_->bytes_written_ = 0;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-07-08 21:31:49 +02:00
|
|
|
|
|
|
|
start = env_->NowMicros();
|
2014-07-09 00:15:00 +02:00
|
|
|
// Write ~96M data
|
|
|
|
for (int64_t i = 0; i < (96 << 10); ++i) {
|
|
|
|
ASSERT_OK(Put(RandomString(&rnd, 32),
|
|
|
|
RandomString(&rnd, (1 << 10) + 1), wo));
|
2014-07-08 21:31:49 +02:00
|
|
|
}
|
|
|
|
elapsed = env_->NowMicros() - start;
|
|
|
|
Close();
|
|
|
|
ASSERT_TRUE(options.rate_limiter->GetTotalBytesThrough() ==
|
|
|
|
env_->bytes_written_);
|
|
|
|
double ratio = env_->bytes_written_ * 1000000 / elapsed / raw_rate;
|
|
|
|
fprintf(stderr, "write rate ratio = %.2lf, expected 0.7\n", ratio);
|
2014-07-26 00:17:06 +02:00
|
|
|
ASSERT_TRUE(ratio < 0.8);
|
2014-07-08 21:31:49 +02:00
|
|
|
|
|
|
|
// # rate limiting with half of the raw_rate
|
|
|
|
options.rate_limiter.reset(
|
2014-07-26 00:17:06 +02:00
|
|
|
NewGenericRateLimiter(static_cast<int64_t>(raw_rate / 2)));
|
2014-07-08 21:31:49 +02:00
|
|
|
env_->bytes_written_ = 0;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-07-08 21:31:49 +02:00
|
|
|
|
|
|
|
start = env_->NowMicros();
|
2014-07-09 00:15:00 +02:00
|
|
|
// Write ~96M data
|
|
|
|
for (int64_t i = 0; i < (96 << 10); ++i) {
|
|
|
|
ASSERT_OK(Put(RandomString(&rnd, 32),
|
|
|
|
RandomString(&rnd, (1 << 10) + 1), wo));
|
2014-07-08 21:31:49 +02:00
|
|
|
}
|
|
|
|
elapsed = env_->NowMicros() - start;
|
|
|
|
Close();
|
|
|
|
ASSERT_TRUE(options.rate_limiter->GetTotalBytesThrough() ==
|
|
|
|
env_->bytes_written_);
|
|
|
|
ratio = env_->bytes_written_ * 1000000 / elapsed / raw_rate;
|
|
|
|
fprintf(stderr, "write rate ratio = %.2lf, expected 0.5\n", ratio);
|
2014-07-26 00:17:06 +02:00
|
|
|
ASSERT_TRUE(ratio < 0.6);
|
2014-07-08 21:31:49 +02:00
|
|
|
}
|
|
|
|
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
namespace {
|
|
|
|
bool HaveOverlappingKeyRanges(
|
|
|
|
const Comparator* c,
|
|
|
|
const SstFileMetaData& a, const SstFileMetaData& b) {
|
|
|
|
if (c->Compare(a.smallestkey, b.smallestkey) >= 0) {
|
|
|
|
if (c->Compare(a.smallestkey, b.largestkey) <= 0) {
|
|
|
|
// b.smallestkey <= a.smallestkey <= b.largestkey
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else if (c->Compare(a.largestkey, b.smallestkey) >= 0) {
|
|
|
|
// a.smallestkey < b.smallestkey <= a.largestkey
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (c->Compare(a.largestkey, b.largestkey) <= 0) {
|
|
|
|
if (c->Compare(a.largestkey, b.smallestkey) >= 0) {
|
|
|
|
// b.smallestkey <= a.largestkey <= b.largestkey
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else if (c->Compare(a.smallestkey, b.largestkey) <= 0) {
|
|
|
|
// a.smallestkey <= b.largestkey < a.largestkey
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Identifies all files between level "min_level" and "max_level"
|
|
|
|
// which has overlapping key range with "input_file_meta".
|
|
|
|
void GetOverlappingFileNumbersForLevelCompaction(
|
|
|
|
const ColumnFamilyMetaData& cf_meta,
|
|
|
|
const Comparator* comparator,
|
|
|
|
int min_level, int max_level,
|
|
|
|
const SstFileMetaData* input_file_meta,
|
|
|
|
std::set<std::string>* overlapping_file_names) {
|
|
|
|
std::set<const SstFileMetaData*> overlapping_files;
|
|
|
|
overlapping_files.insert(input_file_meta);
|
|
|
|
for (int m = min_level; m <= max_level; ++m) {
|
|
|
|
for (auto& file : cf_meta.levels[m].files) {
|
|
|
|
for (auto* included_file : overlapping_files) {
|
|
|
|
if (HaveOverlappingKeyRanges(
|
|
|
|
comparator, *included_file, file)) {
|
|
|
|
overlapping_files.insert(&file);
|
|
|
|
overlapping_file_names->insert(file.name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VerifyCompactionResult(
|
|
|
|
const ColumnFamilyMetaData& cf_meta,
|
|
|
|
const std::set<std::string>& overlapping_file_numbers) {
|
2014-12-22 23:56:27 +01:00
|
|
|
#ifndef NDEBUG
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
for (auto& level : cf_meta.levels) {
|
|
|
|
for (auto& file : level.files) {
|
|
|
|
assert(overlapping_file_numbers.find(file.name) ==
|
|
|
|
overlapping_file_numbers.end());
|
|
|
|
}
|
|
|
|
}
|
2014-12-22 23:56:27 +01:00
|
|
|
#endif
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const SstFileMetaData* PickFileRandomly(
|
|
|
|
const ColumnFamilyMetaData& cf_meta,
|
|
|
|
Random* rand,
|
|
|
|
int* level = nullptr) {
|
|
|
|
auto file_id = rand->Uniform(static_cast<int>(
|
|
|
|
cf_meta.file_count)) + 1;
|
|
|
|
for (auto& level_meta : cf_meta.levels) {
|
|
|
|
if (file_id <= level_meta.files.size()) {
|
|
|
|
if (level != nullptr) {
|
|
|
|
*level = level_meta.level;
|
|
|
|
}
|
|
|
|
auto result = rand->Uniform(file_id);
|
|
|
|
return &(level_meta.files[result]);
|
|
|
|
}
|
|
|
|
file_id -= level_meta.files.size();
|
|
|
|
}
|
|
|
|
assert(false);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2015-04-14 04:30:40 +02:00
|
|
|
// TODO t6534343 -- Don't run two level 0 CompactFiles concurrently
|
|
|
|
TEST_F(DBTest, DISABLED_CompactFilesOnLevelCompaction) {
|
2014-11-07 23:57:51 +01:00
|
|
|
const int kTestKeySize = 16;
|
|
|
|
const int kTestValueSize = 984;
|
|
|
|
const int kEntrySize = kTestKeySize + kTestValueSize;
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
const int kEntriesPerBuffer = 100;
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.target_file_size_base = options.write_buffer_size;
|
|
|
|
options.max_bytes_for_level_base = options.target_file_size_base * 2;
|
|
|
|
options.level0_stop_writes_trigger = 2;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
for (int key = 64 * kEntriesPerBuffer; key >= 0; --key) {
|
2014-11-25 05:44:49 +01:00
|
|
|
ASSERT_OK(Put(1, ToString(key), RandomString(&rnd, kTestValueSize)));
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
2014-11-11 22:47:22 +01:00
|
|
|
int output_level = static_cast<int>(cf_meta.levels.size()) - 1;
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
for (int file_picked = 5; file_picked > 0; --file_picked) {
|
|
|
|
std::set<std::string> overlapping_file_names;
|
|
|
|
std::vector<std::string> compaction_input_file_names;
|
|
|
|
for (int f = 0; f < file_picked; ++f) {
|
|
|
|
int level;
|
|
|
|
auto file_meta = PickFileRandomly(cf_meta, &rnd, &level);
|
|
|
|
compaction_input_file_names.push_back(file_meta->name);
|
|
|
|
GetOverlappingFileNumbersForLevelCompaction(
|
|
|
|
cf_meta, options.comparator, level, output_level,
|
|
|
|
file_meta, &overlapping_file_names);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->CompactFiles(
|
|
|
|
CompactionOptions(), handles_[1],
|
|
|
|
compaction_input_file_names,
|
|
|
|
output_level));
|
|
|
|
|
|
|
|
// Make sure all overlapping files do not exist after compaction
|
|
|
|
dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
|
|
|
VerifyCompactionResult(cf_meta, overlapping_file_names);
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure all key-values are still there.
|
|
|
|
for (int key = 64 * kEntriesPerBuffer; key >= 0; --key) {
|
2014-11-25 05:44:49 +01:00
|
|
|
ASSERT_NE(Get(1, ToString(key)), "NOT_FOUND");
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CompactFilesOnUniversalCompaction) {
|
2014-11-07 23:57:51 +01:00
|
|
|
const int kTestKeySize = 16;
|
|
|
|
const int kTestValueSize = 984;
|
|
|
|
const int kEntrySize = kTestKeySize + kTestValueSize;
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
const int kEntriesPerBuffer = 10;
|
|
|
|
|
|
|
|
ChangeCompactOptions();
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
2015-03-30 23:04:21 +02:00
|
|
|
options.num_levels = 1;
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
options.target_file_size_base = options.write_buffer_size;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
ASSERT_EQ(options.compaction_style, kCompactionStyleUniversal);
|
|
|
|
Random rnd(301);
|
|
|
|
for (int key = 1024 * kEntriesPerBuffer; key >= 0; --key) {
|
2014-11-25 05:44:49 +01:00
|
|
|
ASSERT_OK(Put(1, ToString(key), RandomString(&rnd, kTestValueSize)));
|
CompactFiles, EventListener and GetDatabaseMetaData
Summary:
This diff adds three sets of APIs to RocksDB.
= GetColumnFamilyMetaData =
* This APIs allow users to obtain the current state of a RocksDB instance on one column family.
* See GetColumnFamilyMetaData in include/rocksdb/db.h
= EventListener =
* A virtual class that allows users to implement a set of
call-back functions which will be called when specific
events of a RocksDB instance happens.
* To register EventListener, simply insert an EventListener to ColumnFamilyOptions::listeners
= CompactFiles =
* CompactFiles API inputs a set of file numbers and an output level, and RocksDB
will try to compact those files into the specified level.
= Example =
* Example code can be found in example/compact_files_example.cc, which implements
a simple external compactor using EventListener, GetColumnFamilyMetaData, and
CompactFiles API.
Test Plan:
listener_test
compactor_test
example/compact_files_example
export ROCKSDB_TESTS=CompactFiles
db_test
export ROCKSDB_TESTS=MetaData
db_test
Reviewers: ljin, igor, rven, sdong
Reviewed By: sdong
Subscribers: MarkCallaghan, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D24705
2014-11-07 23:45:18 +01:00
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
|
|
|
std::vector<std::string> compaction_input_file_names;
|
|
|
|
for (auto file : cf_meta.levels[0].files) {
|
|
|
|
if (rnd.OneIn(2)) {
|
|
|
|
compaction_input_file_names.push_back(file.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (compaction_input_file_names.size() == 0) {
|
|
|
|
compaction_input_file_names.push_back(
|
|
|
|
cf_meta.levels[0].files[0].name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// expect fail since universal compaction only allow L0 output
|
|
|
|
ASSERT_TRUE(!dbfull()->CompactFiles(
|
|
|
|
CompactionOptions(), handles_[1],
|
|
|
|
compaction_input_file_names, 1).ok());
|
|
|
|
|
|
|
|
// expect ok and verify the compacted files no longer exist.
|
|
|
|
ASSERT_OK(dbfull()->CompactFiles(
|
|
|
|
CompactionOptions(), handles_[1],
|
|
|
|
compaction_input_file_names, 0));
|
|
|
|
|
|
|
|
dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
|
|
|
VerifyCompactionResult(
|
|
|
|
cf_meta,
|
|
|
|
std::set<std::string>(compaction_input_file_names.begin(),
|
|
|
|
compaction_input_file_names.end()));
|
|
|
|
|
|
|
|
compaction_input_file_names.clear();
|
|
|
|
|
|
|
|
// Pick the first and the last file, expect everything is
|
|
|
|
// compacted into one single file.
|
|
|
|
compaction_input_file_names.push_back(
|
|
|
|
cf_meta.levels[0].files[0].name);
|
|
|
|
compaction_input_file_names.push_back(
|
|
|
|
cf_meta.levels[0].files[
|
|
|
|
cf_meta.levels[0].files.size() - 1].name);
|
|
|
|
ASSERT_OK(dbfull()->CompactFiles(
|
|
|
|
CompactionOptions(), handles_[1],
|
|
|
|
compaction_input_file_names, 0));
|
|
|
|
|
|
|
|
dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
|
|
|
ASSERT_EQ(cf_meta.levels[0].files.size(), 1U);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, TableOptionsSanitizeTest) {
|
2014-08-21 00:53:39 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-08-21 00:53:39 +02:00
|
|
|
ASSERT_EQ(db_->GetOptions().allow_mmap_reads, false);
|
|
|
|
|
|
|
|
options.table_factory.reset(new PlainTableFactory());
|
|
|
|
options.prefix_extractor.reset(NewNoopTransform());
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
2014-10-18 06:18:36 +02:00
|
|
|
|
|
|
|
// Test for check of prefix_extractor when hash index is used for
|
|
|
|
// block-based table
|
|
|
|
BlockBasedTableOptions to;
|
|
|
|
to.index_type = BlockBasedTableOptions::kHashSearch;
|
2014-10-31 23:08:10 +01:00
|
|
|
options = CurrentOptions();
|
2014-10-18 06:18:36 +02:00
|
|
|
options.create_if_missing = true;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(to));
|
2014-10-29 20:00:01 +01:00
|
|
|
ASSERT_TRUE(TryReopen(options).IsInvalidArgument());
|
2014-10-18 06:18:36 +02:00
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
2014-10-29 20:00:01 +01:00
|
|
|
ASSERT_OK(TryReopen(options));
|
2014-08-21 00:53:39 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, SanitizeNumThreads) {
|
2014-11-03 23:11:33 +01:00
|
|
|
for (int attempt = 0; attempt < 2; attempt++) {
|
|
|
|
const size_t kTotalTasks = 8;
|
|
|
|
SleepingBackgroundTask sleeping_tasks[kTotalTasks];
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
if (attempt == 0) {
|
|
|
|
options.max_background_compactions = 3;
|
|
|
|
options.max_background_flushes = 2;
|
|
|
|
}
|
|
|
|
options.create_if_missing = true;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < kTotalTasks; i++) {
|
|
|
|
// Insert 5 tasks to low priority queue and 5 tasks to high priority queue
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_tasks[i],
|
|
|
|
(i < 4) ? Env::Priority::LOW : Env::Priority::HIGH);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait 100 milliseconds for they are scheduled.
|
|
|
|
env_->SleepForMicroseconds(100000);
|
|
|
|
|
|
|
|
// pool size 3, total task 4. Queue size should be 1.
|
|
|
|
ASSERT_EQ(1U, options.env->GetThreadPoolQueueLen(Env::Priority::LOW));
|
|
|
|
// pool size 2, total task 4. Queue size should be 2.
|
|
|
|
ASSERT_EQ(2U, options.env->GetThreadPoolQueueLen(Env::Priority::HIGH));
|
|
|
|
|
|
|
|
for (size_t i = 0; i < kTotalTasks; i++) {
|
|
|
|
sleeping_tasks[i].WakeUp();
|
|
|
|
sleeping_tasks[i].WaitUntilDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_OK(Put("abc", "def"));
|
|
|
|
ASSERT_EQ("def", Get("abc"));
|
|
|
|
Flush();
|
|
|
|
ASSERT_EQ("def", Get("abc"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DBIteratorBoundTest) {
|
2014-10-31 23:08:10 +01:00
|
|
|
Options options = CurrentOptions();
|
2014-09-04 19:48:24 +02:00
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
|
|
|
|
options.prefix_extractor = nullptr;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-09-04 19:48:24 +02:00
|
|
|
ASSERT_OK(Put("a", "0"));
|
|
|
|
ASSERT_OK(Put("foo", "bar"));
|
|
|
|
ASSERT_OK(Put("foo1", "bar1"));
|
|
|
|
ASSERT_OK(Put("g1", "0"));
|
|
|
|
|
|
|
|
// testing basic case with no iterate_upper_bound and no prefix_extractor
|
|
|
|
{
|
|
|
|
ReadOptions ro;
|
|
|
|
ro.iterate_upper_bound = nullptr;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
|
|
|
|
|
|
|
iter->Seek("foo");
|
|
|
|
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Slice("foo")), 0);
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Slice("foo1")), 0);
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Slice("g1")), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// testing iterate_upper_bound and forward iterator
|
|
|
|
// to make sure it stops at bound
|
|
|
|
{
|
|
|
|
ReadOptions ro;
|
|
|
|
// iterate_upper_bound points beyond the last expected entry
|
2014-09-05 09:47:54 +02:00
|
|
|
Slice prefix("foo2");
|
|
|
|
ro.iterate_upper_bound = &prefix;
|
2014-09-04 19:48:24 +02:00
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
|
|
|
|
|
|
|
iter->Seek("foo");
|
|
|
|
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Slice("foo")), 0);
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(("foo1")), 0);
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
// should stop here...
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
}
|
|
|
|
|
|
|
|
// prefix is the first letter of the key
|
|
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-09-04 19:48:24 +02:00
|
|
|
ASSERT_OK(Put("a", "0"));
|
|
|
|
ASSERT_OK(Put("foo", "bar"));
|
|
|
|
ASSERT_OK(Put("foo1", "bar1"));
|
|
|
|
ASSERT_OK(Put("g1", "0"));
|
|
|
|
|
|
|
|
// testing with iterate_upper_bound and prefix_extractor
|
|
|
|
// Seek target and iterate_upper_bound are not is same prefix
|
|
|
|
// This should be an error
|
|
|
|
{
|
|
|
|
ReadOptions ro;
|
2014-09-05 09:47:54 +02:00
|
|
|
Slice prefix("g1");
|
|
|
|
ro.iterate_upper_bound = &prefix;
|
2014-09-04 19:48:24 +02:00
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
|
|
|
|
|
|
|
iter->Seek("foo");
|
|
|
|
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
ASSERT_TRUE(iter->status().IsInvalidArgument());
|
|
|
|
}
|
|
|
|
|
|
|
|
// testing that iterate_upper_bound prevents iterating over deleted items
|
|
|
|
// if the bound has already reached
|
|
|
|
{
|
|
|
|
options.prefix_extractor = nullptr;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-09-04 19:48:24 +02:00
|
|
|
ASSERT_OK(Put("a", "0"));
|
|
|
|
ASSERT_OK(Put("b", "0"));
|
|
|
|
ASSERT_OK(Put("b1", "0"));
|
|
|
|
ASSERT_OK(Put("c", "0"));
|
|
|
|
ASSERT_OK(Put("d", "0"));
|
|
|
|
ASSERT_OK(Put("e", "0"));
|
|
|
|
ASSERT_OK(Delete("c"));
|
|
|
|
ASSERT_OK(Delete("d"));
|
|
|
|
|
|
|
|
// base case with no bound
|
|
|
|
ReadOptions ro;
|
|
|
|
ro.iterate_upper_bound = nullptr;
|
|
|
|
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
|
|
|
|
|
|
|
iter->Seek("b");
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Slice("b")), 0);
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(("b1")), 0);
|
|
|
|
|
|
|
|
perf_context.Reset();
|
|
|
|
iter->Next();
|
|
|
|
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(static_cast<int>(perf_context.internal_delete_skipped_count), 2);
|
|
|
|
|
|
|
|
// now testing with iterate_bound
|
2014-09-05 09:47:54 +02:00
|
|
|
Slice prefix("c");
|
|
|
|
ro.iterate_upper_bound = &prefix;
|
2014-09-04 19:48:24 +02:00
|
|
|
|
|
|
|
iter.reset(db_->NewIterator(ro));
|
|
|
|
|
|
|
|
perf_context.Reset();
|
|
|
|
|
|
|
|
iter->Seek("b");
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Slice("b")), 0);
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(("b1")), 0);
|
|
|
|
|
|
|
|
iter->Next();
|
|
|
|
// the iteration should stop as soon as the the bound key is reached
|
|
|
|
// even though the key is deleted
|
|
|
|
// hence internal_delete_skipped_count should be 0
|
|
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
ASSERT_EQ(static_cast<int>(perf_context.internal_delete_skipped_count), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, WriteSingleThreadEntry) {
|
2014-09-06 00:20:05 +02:00
|
|
|
std::vector<std::thread> threads;
|
|
|
|
dbfull()->TEST_LockMutex();
|
|
|
|
auto w = dbfull()->TEST_BeginWrite();
|
|
|
|
threads.emplace_back([&] { Put("a", "b"); });
|
|
|
|
env_->SleepForMicroseconds(10000);
|
|
|
|
threads.emplace_back([&] { Flush(); });
|
|
|
|
env_->SleepForMicroseconds(10000);
|
|
|
|
dbfull()->TEST_UnlockMutex();
|
|
|
|
dbfull()->TEST_LockMutex();
|
|
|
|
dbfull()->TEST_EndWrite(w);
|
|
|
|
dbfull()->TEST_UnlockMutex();
|
|
|
|
|
|
|
|
for (auto& t : threads) {
|
|
|
|
t.join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DisableDataSyncTest) {
|
2015-01-22 20:43:38 +01:00
|
|
|
env_->sync_counter_.store(0);
|
2014-09-15 20:32:01 +02:00
|
|
|
// iter 0 -- no sync
|
|
|
|
// iter 1 -- sync
|
|
|
|
for (int iter = 0; iter < 2; ++iter) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.disableDataSync = iter == 0;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.env = env_;
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-10-29 20:00:01 +01:00
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
2014-09-15 20:32:01 +02:00
|
|
|
|
|
|
|
MakeTables(10, "a", "z");
|
|
|
|
Compact("a", "z");
|
|
|
|
|
|
|
|
if (iter == 0) {
|
|
|
|
ASSERT_EQ(env_->sync_counter_.load(), 0);
|
|
|
|
} else {
|
|
|
|
ASSERT_GT(env_->sync_counter_.load(), 0);
|
|
|
|
}
|
2014-10-29 19:59:18 +01:00
|
|
|
Destroy(options);
|
2014-09-15 20:32:01 +02:00
|
|
|
}
|
|
|
|
}
|
2014-09-06 00:20:05 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DynamicMemtableOptions) {
|
2014-10-07 19:40:45 +02:00
|
|
|
const uint64_t k64KB = 1 << 16;
|
|
|
|
const uint64_t k128KB = 1 << 17;
|
|
|
|
const uint64_t k5KB = 5 * 1024;
|
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.compression = kNoCompression;
|
2014-11-04 20:07:36 +01:00
|
|
|
options.max_background_compactions = 1;
|
2014-10-07 19:40:45 +02:00
|
|
|
options.max_mem_compaction_level = 0;
|
|
|
|
options.write_buffer_size = k64KB;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
// Don't trigger compact/slowdown/stop
|
|
|
|
options.level0_file_num_compaction_trigger = 1024;
|
|
|
|
options.level0_slowdown_writes_trigger = 1024;
|
|
|
|
options.level0_stop_writes_trigger = 1024;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-07 19:40:45 +02:00
|
|
|
|
|
|
|
auto gen_l0_kb = [this](int size) {
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < size; i++) {
|
2014-10-17 01:57:59 +02:00
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
2014-10-07 19:40:45 +02:00
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
};
|
|
|
|
|
2014-10-17 01:57:59 +02:00
|
|
|
// Test write_buffer_size
|
2014-10-07 19:40:45 +02:00
|
|
|
gen_l0_kb(64);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
2014-11-04 20:33:57 +01:00
|
|
|
ASSERT_LT(SizeAtLevel(0), k64KB + k5KB);
|
|
|
|
ASSERT_GT(SizeAtLevel(0), k64KB - k5KB);
|
2014-10-07 19:40:45 +02:00
|
|
|
|
|
|
|
// Clean up L0
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-10-07 19:40:45 +02:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
|
|
|
|
// Increase buffer size
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-07 19:40:45 +02:00
|
|
|
{"write_buffer_size", "131072"},
|
|
|
|
}));
|
|
|
|
|
|
|
|
// The existing memtable is still 64KB in size, after it becomes immutable,
|
|
|
|
// the next memtable will be 128KB in size. Write 256KB total, we should
|
|
|
|
// have a 64KB L0 file, a 128KB L0 file, and a memtable with 64KB data
|
|
|
|
gen_l0_kb(256);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 2);
|
2014-11-04 20:33:57 +01:00
|
|
|
ASSERT_LT(SizeAtLevel(0), k128KB + k64KB + 2 * k5KB);
|
|
|
|
ASSERT_GT(SizeAtLevel(0), k128KB + k64KB - 2 * k5KB);
|
2014-10-17 01:57:59 +02:00
|
|
|
|
|
|
|
// Test max_write_buffer_number
|
|
|
|
// Block compaction thread, which will also block the flushes because
|
|
|
|
// max_background_flushes == 0, so flushes are getting executed by the
|
|
|
|
// compaction thread
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
2014-10-17 19:09:45 +02:00
|
|
|
SleepingBackgroundTask sleeping_task_low1;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low1,
|
2014-10-17 01:57:59 +02:00
|
|
|
Env::Priority::LOW);
|
|
|
|
// Start from scratch and disable compaction/flush. Flush can only happen
|
|
|
|
// during compaction but trigger is pretty high
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
options.disable_auto_compactions = true;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-17 01:57:59 +02:00
|
|
|
|
|
|
|
// Put until timeout, bounded by 256 puts. We should see timeout at ~128KB
|
|
|
|
int count = 0;
|
|
|
|
Random rnd(301);
|
|
|
|
WriteOptions wo;
|
2015-03-30 23:04:21 +02:00
|
|
|
wo.timeout_hint_us = 100000; // Reasonabley long timeout to make sure sleep
|
|
|
|
// triggers but not forever.
|
|
|
|
|
|
|
|
std::atomic<int> sleep_count(0);
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBImpl::DelayWrite:TimedWait",
|
2015-04-14 10:55:19 +02:00
|
|
|
[&](void* arg) { sleep_count.fetch_add(1); });
|
2015-03-30 23:04:21 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
2014-10-17 01:57:59 +02:00
|
|
|
|
|
|
|
while (Put(Key(count), RandomString(&rnd, 1024), wo).ok() && count < 256) {
|
|
|
|
count++;
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_GT(sleep_count.load(), 0);
|
2014-11-04 20:33:57 +01:00
|
|
|
ASSERT_GT(static_cast<double>(count), 128 * 0.8);
|
|
|
|
ASSERT_LT(static_cast<double>(count), 128 * 1.2);
|
2014-10-17 01:57:59 +02:00
|
|
|
|
2014-10-17 19:09:45 +02:00
|
|
|
sleeping_task_low1.WakeUp();
|
|
|
|
sleeping_task_low1.WaitUntilDone();
|
2014-10-17 01:57:59 +02:00
|
|
|
|
|
|
|
// Increase
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-17 01:57:59 +02:00
|
|
|
{"max_write_buffer_number", "8"},
|
|
|
|
}));
|
|
|
|
// Clean up memtable and L0
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-10-17 01:57:59 +02:00
|
|
|
|
2014-10-17 19:09:45 +02:00
|
|
|
SleepingBackgroundTask sleeping_task_low2;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low2,
|
2014-10-17 01:57:59 +02:00
|
|
|
Env::Priority::LOW);
|
|
|
|
count = 0;
|
2015-03-30 23:04:21 +02:00
|
|
|
sleep_count.store(0);
|
2014-10-17 01:57:59 +02:00
|
|
|
while (Put(Key(count), RandomString(&rnd, 1024), wo).ok() && count < 1024) {
|
|
|
|
count++;
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_GT(sleep_count.load(), 0);
|
2014-11-04 20:33:57 +01:00
|
|
|
ASSERT_GT(static_cast<double>(count), 512 * 0.8);
|
|
|
|
ASSERT_LT(static_cast<double>(count), 512 * 1.2);
|
2014-10-17 19:09:45 +02:00
|
|
|
sleeping_task_low2.WakeUp();
|
|
|
|
sleeping_task_low2.WaitUntilDone();
|
2014-10-17 01:57:59 +02:00
|
|
|
|
|
|
|
// Decrease
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-17 01:57:59 +02:00
|
|
|
{"max_write_buffer_number", "4"},
|
|
|
|
}));
|
|
|
|
// Clean up memtable and L0
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-10-17 01:57:59 +02:00
|
|
|
|
2014-10-17 19:09:45 +02:00
|
|
|
SleepingBackgroundTask sleeping_task_low3;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low3,
|
|
|
|
Env::Priority::LOW);
|
2015-03-30 23:04:21 +02:00
|
|
|
|
2014-10-17 01:57:59 +02:00
|
|
|
count = 0;
|
2015-03-30 23:04:21 +02:00
|
|
|
sleep_count.store(0);
|
2014-10-17 01:57:59 +02:00
|
|
|
while (Put(Key(count), RandomString(&rnd, 1024), wo).ok() && count < 1024) {
|
|
|
|
count++;
|
|
|
|
}
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_GT(sleep_count.load(), 0);
|
2014-11-04 20:33:57 +01:00
|
|
|
ASSERT_GT(static_cast<double>(count), 256 * 0.8);
|
|
|
|
ASSERT_LT(static_cast<double>(count), 266 * 1.2);
|
2014-10-17 19:09:45 +02:00
|
|
|
sleeping_task_low3.WakeUp();
|
|
|
|
sleeping_task_low3.WaitUntilDone();
|
2015-03-30 23:04:21 +02:00
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
2014-10-07 19:40:45 +02:00
|
|
|
}
|
|
|
|
|
2014-11-20 19:49:32 +01:00
|
|
|
#if ROCKSDB_USING_THREAD_STATUS
|
2015-02-17 19:13:52 +01:00
|
|
|
namespace {
|
|
|
|
void VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
|
|
|
|
int expected_count) {
|
|
|
|
int op_count = 0;
|
|
|
|
std::vector<ThreadStatus> thread_list;
|
|
|
|
ASSERT_OK(env->GetThreadList(&thread_list));
|
|
|
|
for (auto thread : thread_list) {
|
|
|
|
if (thread.operation_type == op_type) {
|
|
|
|
op_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ASSERT_EQ(op_count, expected_count);
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, GetThreadStatus) {
|
2014-11-20 19:49:32 +01:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
2014-11-21 06:13:18 +01:00
|
|
|
options.enable_thread_tracking = true;
|
|
|
|
TryReopen(options);
|
2014-11-20 19:49:32 +01:00
|
|
|
|
|
|
|
std::vector<ThreadStatus> thread_list;
|
2014-12-22 21:20:17 +01:00
|
|
|
Status s = env_->GetThreadList(&thread_list);
|
2014-11-20 19:49:32 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
// repeat the test with differet number of high / low priority threads
|
|
|
|
const int kTestCount = 3;
|
|
|
|
const unsigned int kHighPriCounts[kTestCount] = {3, 2, 5};
|
|
|
|
const unsigned int kLowPriCounts[kTestCount] = {10, 15, 3};
|
|
|
|
for (int test = 0; test < kTestCount; ++test) {
|
|
|
|
// Change the number of threads in high / low priority pool.
|
|
|
|
env_->SetBackgroundThreads(kHighPriCounts[test], Env::HIGH);
|
|
|
|
env_->SetBackgroundThreads(kLowPriCounts[test], Env::LOW);
|
|
|
|
// Wait to ensure the all threads has been registered
|
|
|
|
env_->SleepForMicroseconds(100000);
|
2014-12-22 21:20:17 +01:00
|
|
|
s = env_->GetThreadList(&thread_list);
|
2014-11-20 19:49:32 +01:00
|
|
|
ASSERT_OK(s);
|
2014-12-30 19:39:13 +01:00
|
|
|
unsigned int thread_type_counts[ThreadStatus::NUM_THREAD_TYPES];
|
2014-11-20 19:49:32 +01:00
|
|
|
memset(thread_type_counts, 0, sizeof(thread_type_counts));
|
|
|
|
for (auto thread : thread_list) {
|
2014-12-30 19:39:13 +01:00
|
|
|
ASSERT_LT(thread.thread_type, ThreadStatus::NUM_THREAD_TYPES);
|
2014-11-20 19:49:32 +01:00
|
|
|
thread_type_counts[thread.thread_type]++;
|
|
|
|
}
|
|
|
|
// Verify the total number of threades
|
|
|
|
ASSERT_EQ(
|
2015-01-13 09:38:09 +01:00
|
|
|
thread_type_counts[ThreadStatus::HIGH_PRIORITY] +
|
|
|
|
thread_type_counts[ThreadStatus::LOW_PRIORITY],
|
2014-11-20 19:49:32 +01:00
|
|
|
kHighPriCounts[test] + kLowPriCounts[test]);
|
|
|
|
// Verify the number of high-priority threads
|
|
|
|
ASSERT_EQ(
|
2014-12-30 19:39:13 +01:00
|
|
|
thread_type_counts[ThreadStatus::HIGH_PRIORITY],
|
2014-11-20 19:49:32 +01:00
|
|
|
kHighPriCounts[test]);
|
|
|
|
// Verify the number of low-priority threads
|
|
|
|
ASSERT_EQ(
|
2014-12-30 19:39:13 +01:00
|
|
|
thread_type_counts[ThreadStatus::LOW_PRIORITY],
|
2014-11-20 19:49:32 +01:00
|
|
|
kLowPriCounts[test]);
|
|
|
|
}
|
|
|
|
if (i == 0) {
|
|
|
|
// repeat the test with multiple column families
|
|
|
|
CreateAndReopenWithCF({"pikachu", "about-to-remove"}, options);
|
2014-12-22 21:20:17 +01:00
|
|
|
env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(
|
|
|
|
handles_, true);
|
2014-11-20 19:49:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
db_->DropColumnFamily(handles_[2]);
|
2014-11-22 09:04:41 +01:00
|
|
|
delete handles_[2];
|
2014-11-20 19:49:32 +01:00
|
|
|
handles_.erase(handles_.begin() + 2);
|
2014-12-22 21:20:17 +01:00
|
|
|
env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(
|
|
|
|
handles_, true);
|
2014-11-20 19:49:32 +01:00
|
|
|
Close();
|
2014-12-22 21:20:17 +01:00
|
|
|
env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(
|
|
|
|
handles_, true);
|
2014-11-21 06:13:18 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DisableThreadStatus) {
|
2014-11-21 06:13:18 +01:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.enable_thread_tracking = false;
|
|
|
|
TryReopen(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu", "about-to-remove"}, options);
|
|
|
|
// Verify non of the column family info exists
|
2014-12-22 21:20:17 +01:00
|
|
|
env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(
|
|
|
|
handles_, false);
|
2014-11-20 19:49:32 +01:00
|
|
|
}
|
2015-01-13 09:04:08 +01:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ThreadStatusFlush) {
|
2015-02-17 19:13:52 +01:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
|
|
|
options.enable_thread_tracking = true;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
2015-03-13 18:45:40 +01:00
|
|
|
{"FlushJob::FlushJob()", "DBTest::ThreadStatusFlush:1"},
|
|
|
|
{"DBTest::ThreadStatusFlush:2", "FlushJob::~FlushJob()"},
|
2015-02-17 19:13:52 +01:00
|
|
|
});
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0);
|
|
|
|
|
|
|
|
ASSERT_OK(Put(1, "foo", "v1"));
|
|
|
|
ASSERT_EQ("v1", Get(1, "foo"));
|
|
|
|
VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0);
|
|
|
|
|
|
|
|
Put(1, "k1", std::string(100000, 'x')); // Fill memtable
|
|
|
|
VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0);
|
|
|
|
Put(1, "k2", std::string(100000, 'y')); // Trigger flush
|
|
|
|
// wait for flush to be scheduled
|
|
|
|
env_->SleepForMicroseconds(250000);
|
|
|
|
TEST_SYNC_POINT("DBTest::ThreadStatusFlush:1");
|
|
|
|
VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 1);
|
|
|
|
TEST_SYNC_POINT("DBTest::ThreadStatusFlush:2");
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, ThreadStatusSingleCompaction) {
|
2015-01-13 09:04:08 +01:00
|
|
|
const int kTestKeySize = 16;
|
|
|
|
const int kTestValueSize = 984;
|
|
|
|
const int kEntrySize = kTestKeySize + kTestValueSize;
|
|
|
|
const int kEntriesPerBuffer = 100;
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.target_file_size_base = options.write_buffer_size;
|
|
|
|
options.max_bytes_for_level_base = options.target_file_size_base * 2;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.env = env_;
|
|
|
|
options.enable_thread_tracking = true;
|
|
|
|
const int kNumL0Files = 4;
|
|
|
|
options.level0_file_num_compaction_trigger = kNumL0Files;
|
2015-02-17 19:13:52 +01:00
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
2015-03-03 19:59:36 +01:00
|
|
|
{"DBTest::ThreadStatusSingleCompaction:0", "DBImpl::BGWorkCompaction"},
|
|
|
|
{"CompactionJob::Run():Start", "DBTest::ThreadStatusSingleCompaction:1"},
|
|
|
|
{"DBTest::ThreadStatusSingleCompaction:2", "CompactionJob::Run():End"},
|
2015-02-17 19:13:52 +01:00
|
|
|
});
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
2015-01-13 09:04:08 +01:00
|
|
|
for (int tests = 0; tests < 2; ++tests) {
|
2015-03-13 19:59:00 +01:00
|
|
|
DestroyAndReopen(options);
|
2015-01-13 09:04:08 +01:00
|
|
|
|
|
|
|
Random rnd(301);
|
2015-03-23 23:05:58 +01:00
|
|
|
// The Put Phase.
|
2015-03-13 21:11:08 +01:00
|
|
|
for (int file = 0; file < kNumL0Files; ++file) {
|
|
|
|
for (int key = 0; key < kEntriesPerBuffer; ++key) {
|
|
|
|
ASSERT_OK(Put(ToString(key + file * kEntriesPerBuffer),
|
|
|
|
RandomString(&rnd, kTestValueSize)));
|
|
|
|
}
|
|
|
|
Flush();
|
2015-01-13 09:04:08 +01:00
|
|
|
}
|
2015-03-23 23:05:58 +01:00
|
|
|
// This makes sure a compaction won't be scheduled until
|
|
|
|
// we have done with the above Put Phase.
|
|
|
|
TEST_SYNC_POINT("DBTest::ThreadStatusSingleCompaction:0");
|
2015-03-13 19:59:00 +01:00
|
|
|
ASSERT_GE(NumTableFilesAtLevel(0),
|
|
|
|
options.level0_file_num_compaction_trigger);
|
2015-01-13 09:04:08 +01:00
|
|
|
|
2015-03-23 23:05:58 +01:00
|
|
|
// This makes sure at least one compaction is running.
|
2015-02-17 19:13:52 +01:00
|
|
|
TEST_SYNC_POINT("DBTest::ThreadStatusSingleCompaction:1");
|
2015-03-23 23:05:58 +01:00
|
|
|
|
2015-01-13 09:04:08 +01:00
|
|
|
if (options.enable_thread_tracking) {
|
|
|
|
// expecting one single L0 to L1 compaction
|
2015-02-17 19:13:52 +01:00
|
|
|
VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 1);
|
2015-01-13 09:04:08 +01:00
|
|
|
} else {
|
|
|
|
// If thread tracking is not enabled, compaction count should be 0.
|
2015-02-17 19:13:52 +01:00
|
|
|
VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 0);
|
2015-01-13 09:04:08 +01:00
|
|
|
}
|
2015-03-13 18:45:40 +01:00
|
|
|
// TODO(yhchiang): adding assert to verify each compaction stage.
|
2015-02-17 19:13:52 +01:00
|
|
|
TEST_SYNC_POINT("DBTest::ThreadStatusSingleCompaction:2");
|
2015-01-13 09:04:08 +01:00
|
|
|
|
|
|
|
// repeat the test with disabling thread tracking.
|
|
|
|
options.enable_thread_tracking = false;
|
|
|
|
}
|
2015-02-17 19:13:52 +01:00
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
2015-01-13 09:04:08 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PreShutdownManualCompaction) {
|
2015-03-11 18:31:02 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
ASSERT_EQ(dbfull()->MaxMemCompactionLevel(), 2)
|
|
|
|
<< "Need to update this test to match kMaxMemCompactLevel";
|
|
|
|
|
|
|
|
// iter - 0 with 7 levels
|
|
|
|
// iter - 1 with 3 levels
|
|
|
|
for (int iter = 0; iter < 2; ++iter) {
|
|
|
|
MakeTables(3, "p", "q", 1);
|
|
|
|
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compaction range falls before files
|
|
|
|
Compact(1, "", "c");
|
|
|
|
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compaction range falls after files
|
|
|
|
Compact(1, "r", "z");
|
|
|
|
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compaction range overlaps files
|
|
|
|
Compact(1, "p1", "p9");
|
|
|
|
ASSERT_EQ("0,0,1", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Populate a different range
|
|
|
|
MakeTables(3, "c", "e", 1);
|
|
|
|
ASSERT_EQ("1,1,2", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compact just the new range
|
|
|
|
Compact(1, "b", "f");
|
|
|
|
ASSERT_EQ("0,0,2", FilesPerLevel(1));
|
|
|
|
|
|
|
|
// Compact all
|
|
|
|
MakeTables(1, "a", "z", 1);
|
|
|
|
ASSERT_EQ("0,1,2", FilesPerLevel(1));
|
|
|
|
CancelAllBackgroundWork(db_);
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
|
2015-03-11 18:31:02 +01:00
|
|
|
ASSERT_EQ("0,1,2", FilesPerLevel(1));
|
|
|
|
|
|
|
|
if (iter == 0) {
|
|
|
|
options = CurrentOptions();
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
options.num_levels = 3;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PreShutdownMultipleCompaction) {
|
2015-03-11 18:31:02 +01:00
|
|
|
const int kTestKeySize = 16;
|
|
|
|
const int kTestValueSize = 984;
|
|
|
|
const int kEntrySize = kTestKeySize + kTestValueSize;
|
2015-04-23 17:35:02 +02:00
|
|
|
const int kEntriesPerBuffer = 40;
|
2015-03-11 18:31:02 +01:00
|
|
|
const int kNumL0Files = 4;
|
|
|
|
|
|
|
|
const int kHighPriCount = 3;
|
|
|
|
const int kLowPriCount = 5;
|
|
|
|
env_->SetBackgroundThreads(kHighPriCount, Env::HIGH);
|
|
|
|
env_->SetBackgroundThreads(kLowPriCount, Env::LOW);
|
|
|
|
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.target_file_size_base = options.write_buffer_size;
|
|
|
|
options.max_bytes_for_level_base =
|
|
|
|
options.target_file_size_base * kNumL0Files;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.env = env_;
|
|
|
|
options.enable_thread_tracking = true;
|
|
|
|
options.level0_file_num_compaction_trigger = kNumL0Files;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
options.max_background_compactions = kLowPriCount;
|
2015-03-13 22:51:40 +01:00
|
|
|
options.level0_stop_writes_trigger = 1 << 10;
|
|
|
|
options.level0_slowdown_writes_trigger = 1 << 10;
|
2015-03-11 18:31:02 +01:00
|
|
|
|
|
|
|
TryReopen(options);
|
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
std::vector<ThreadStatus> thread_list;
|
|
|
|
// Delay both flush and compaction
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
2015-03-14 16:00:06 +01:00
|
|
|
{{"FlushJob::FlushJob()", "CompactionJob::Run():Start"},
|
|
|
|
{"CompactionJob::Run():Start",
|
2015-03-11 18:31:02 +01:00
|
|
|
"DBTest::PreShutdownMultipleCompaction:Preshutdown"},
|
2015-04-23 17:35:02 +02:00
|
|
|
{"CompactionJob::Run():Start",
|
|
|
|
"DBTest::PreShutdownMultipleCompaction:VerifyCompaction"},
|
2015-03-11 18:31:02 +01:00
|
|
|
{"DBTest::PreShutdownMultipleCompaction:Preshutdown",
|
2015-03-14 16:00:06 +01:00
|
|
|
"CompactionJob::Run():End"},
|
|
|
|
{"CompactionJob::Run():End",
|
2015-03-11 18:31:02 +01:00
|
|
|
"DBTest::PreShutdownMultipleCompaction:VerifyPreshutdown"}});
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
// Make rocksdb busy
|
|
|
|
int key = 0;
|
|
|
|
// check how many threads are doing compaction using GetThreadList
|
|
|
|
int operation_count[ThreadStatus::NUM_OP_TYPES] = {0};
|
2015-04-23 17:35:02 +02:00
|
|
|
for (int file = 0; file < 16 * kNumL0Files; ++file) {
|
2015-03-11 18:31:02 +01:00
|
|
|
for (int k = 0; k < kEntriesPerBuffer; ++k) {
|
|
|
|
ASSERT_OK(Put(ToString(key++), RandomString(&rnd, kTestValueSize)));
|
|
|
|
}
|
|
|
|
|
|
|
|
Status s = env_->GetThreadList(&thread_list);
|
|
|
|
for (auto thread : thread_list) {
|
|
|
|
operation_count[thread.operation_type]++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Speed up the test
|
2015-04-23 17:35:02 +02:00
|
|
|
if (operation_count[ThreadStatus::OP_FLUSH] > 1 &&
|
|
|
|
operation_count[ThreadStatus::OP_COMPACTION] >
|
2015-03-11 18:31:02 +01:00
|
|
|
0.6 * options.max_background_compactions) {
|
|
|
|
break;
|
|
|
|
}
|
2015-04-23 17:35:02 +02:00
|
|
|
if (file == 15 * kNumL0Files) {
|
|
|
|
TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:Preshutdown");
|
|
|
|
}
|
2015-03-11 18:31:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:Preshutdown");
|
2015-04-23 17:35:02 +02:00
|
|
|
ASSERT_GE(operation_count[ThreadStatus::OP_COMPACTION], 1);
|
2015-03-11 18:31:02 +01:00
|
|
|
CancelAllBackgroundWork(db_);
|
|
|
|
TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:VerifyPreshutdown");
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Record the number of compactions at a time.
|
|
|
|
for (int i = 0; i < ThreadStatus::NUM_OP_TYPES; ++i) {
|
|
|
|
operation_count[i] = 0;
|
|
|
|
}
|
|
|
|
Status s = env_->GetThreadList(&thread_list);
|
|
|
|
for (auto thread : thread_list) {
|
|
|
|
operation_count[thread.operation_type]++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PreShutdownCompactionMiddle) {
|
2015-03-11 18:31:02 +01:00
|
|
|
const int kTestKeySize = 16;
|
|
|
|
const int kTestValueSize = 984;
|
|
|
|
const int kEntrySize = kTestKeySize + kTestValueSize;
|
2015-04-23 17:35:02 +02:00
|
|
|
const int kEntriesPerBuffer = 40;
|
2015-03-11 18:31:02 +01:00
|
|
|
const int kNumL0Files = 4;
|
|
|
|
|
|
|
|
const int kHighPriCount = 3;
|
|
|
|
const int kLowPriCount = 5;
|
|
|
|
env_->SetBackgroundThreads(kHighPriCount, Env::HIGH);
|
|
|
|
env_->SetBackgroundThreads(kLowPriCount, Env::LOW);
|
|
|
|
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.target_file_size_base = options.write_buffer_size;
|
|
|
|
options.max_bytes_for_level_base =
|
|
|
|
options.target_file_size_base * kNumL0Files;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.env = env_;
|
|
|
|
options.enable_thread_tracking = true;
|
|
|
|
options.level0_file_num_compaction_trigger = kNumL0Files;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
options.max_background_compactions = kLowPriCount;
|
2015-03-13 22:51:40 +01:00
|
|
|
options.level0_stop_writes_trigger = 1 << 10;
|
|
|
|
options.level0_slowdown_writes_trigger = 1 << 10;
|
2015-03-11 18:31:02 +01:00
|
|
|
|
|
|
|
TryReopen(options);
|
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
std::vector<ThreadStatus> thread_list;
|
|
|
|
// Delay both flush and compaction
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
2015-04-23 17:35:02 +02:00
|
|
|
{{"DBTest::PreShutdownCompactionMiddle:Preshutdown",
|
2015-03-14 16:21:53 +01:00
|
|
|
"CompactionJob::Run():Inprogress"},
|
2015-04-23 17:35:02 +02:00
|
|
|
{"CompactionJob::Run():Start",
|
|
|
|
"DBTest::PreShutdownCompactionMiddle:VerifyCompaction"},
|
2015-03-14 16:21:53 +01:00
|
|
|
{"CompactionJob::Run():Inprogress", "CompactionJob::Run():End"},
|
|
|
|
{"CompactionJob::Run():End",
|
2015-04-23 17:35:02 +02:00
|
|
|
"DBTest::PreShutdownCompactionMiddle:VerifyPreshutdown"}});
|
2015-03-11 18:31:02 +01:00
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
// Make rocksdb busy
|
|
|
|
int key = 0;
|
|
|
|
// check how many threads are doing compaction using GetThreadList
|
|
|
|
int operation_count[ThreadStatus::NUM_OP_TYPES] = {0};
|
2015-04-23 17:35:02 +02:00
|
|
|
for (int file = 0; file < 16 * kNumL0Files; ++file) {
|
2015-03-11 18:31:02 +01:00
|
|
|
for (int k = 0; k < kEntriesPerBuffer; ++k) {
|
|
|
|
ASSERT_OK(Put(ToString(key++), RandomString(&rnd, kTestValueSize)));
|
|
|
|
}
|
|
|
|
|
|
|
|
Status s = env_->GetThreadList(&thread_list);
|
|
|
|
for (auto thread : thread_list) {
|
|
|
|
operation_count[thread.operation_type]++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Speed up the test
|
2015-04-23 17:35:02 +02:00
|
|
|
if (operation_count[ThreadStatus::OP_FLUSH] > 1 &&
|
|
|
|
operation_count[ThreadStatus::OP_COMPACTION] >
|
2015-03-11 18:31:02 +01:00
|
|
|
0.6 * options.max_background_compactions) {
|
|
|
|
break;
|
|
|
|
}
|
2015-04-23 17:35:02 +02:00
|
|
|
if (file == 15 * kNumL0Files) {
|
|
|
|
TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:VerifyCompaction");
|
|
|
|
}
|
2015-03-11 18:31:02 +01:00
|
|
|
}
|
|
|
|
|
2015-04-23 17:35:02 +02:00
|
|
|
ASSERT_GE(operation_count[ThreadStatus::OP_COMPACTION], 1);
|
2015-03-11 18:31:02 +01:00
|
|
|
CancelAllBackgroundWork(db_);
|
2015-04-23 17:35:02 +02:00
|
|
|
TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:Preshutdown");
|
|
|
|
TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:VerifyPreshutdown");
|
2015-03-11 18:31:02 +01:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
// Record the number of compactions at a time.
|
|
|
|
for (int i = 0; i < ThreadStatus::NUM_OP_TYPES; ++i) {
|
|
|
|
operation_count[i] = 0;
|
|
|
|
}
|
|
|
|
Status s = env_->GetThreadList(&thread_list);
|
|
|
|
for (auto thread : thread_list) {
|
|
|
|
operation_count[thread.operation_type]++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0);
|
|
|
|
}
|
|
|
|
|
2014-11-20 19:49:32 +01:00
|
|
|
#endif // ROCKSDB_USING_THREAD_STATUS
|
|
|
|
|
2015-06-09 19:39:49 +02:00
|
|
|
TEST_F(DBTest, FlushOnDestroy) {
|
|
|
|
WriteOptions wo;
|
|
|
|
wo.disableWAL = true;
|
|
|
|
ASSERT_OK(Put("foo", "v1", wo));
|
|
|
|
CancelAllBackgroundWork(db_);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DynamicLevelMaxBytesBase) {
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
// Use InMemoryEnv, or it would be too slow.
|
|
|
|
unique_ptr<Env> env(new MockEnv(env_));
|
|
|
|
|
|
|
|
const int kNKeys = 1000;
|
|
|
|
int keys[kNKeys];
|
|
|
|
|
|
|
|
auto verify_func = [&]() {
|
|
|
|
for (int i = 0; i < kNKeys; i++) {
|
|
|
|
ASSERT_NE("NOT_FOUND", Get(Key(i)));
|
|
|
|
ASSERT_NE("NOT_FOUND", Get(Key(kNKeys * 2 + i)));
|
|
|
|
if (i < kNKeys / 10) {
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(Key(kNKeys + keys[i])));
|
|
|
|
} else {
|
|
|
|
ASSERT_NE("NOT_FOUND", Get(Key(kNKeys + keys[i])));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
for (int ordered_insert = 0; ordered_insert <= 1; ordered_insert++) {
|
|
|
|
for (int i = 0; i < kNKeys; i++) {
|
|
|
|
keys[i] = i;
|
|
|
|
}
|
|
|
|
if (ordered_insert == 0) {
|
|
|
|
std::random_shuffle(std::begin(keys), std::end(keys));
|
|
|
|
}
|
|
|
|
for (int max_background_compactions = 1; max_background_compactions < 4;
|
|
|
|
max_background_compactions += 2) {
|
|
|
|
Options options;
|
|
|
|
options.env = env.get();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_write_buffer_size = 2048;
|
|
|
|
options.write_buffer_size = 2048;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 2;
|
|
|
|
options.target_file_size_base = 2048;
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
options.max_bytes_for_level_base = 10240;
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
2015-05-16 00:52:51 +02:00
|
|
|
options.soft_rate_limit = 1.1;
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
options.max_background_compactions = max_background_compactions;
|
|
|
|
options.num_levels = 5;
|
|
|
|
|
2015-03-10 20:35:15 +01:00
|
|
|
options.compression_per_level.resize(3);
|
|
|
|
options.compression_per_level[0] = kNoCompression;
|
|
|
|
options.compression_per_level[1] = kLZ4Compression;
|
|
|
|
options.compression_per_level[2] = kSnappyCompression;
|
|
|
|
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
for (int i = 0; i < kNKeys; i++) {
|
|
|
|
int key = keys[i];
|
|
|
|
ASSERT_OK(Put(Key(kNKeys + key), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Put(Key(key), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Put(Key(kNKeys * 2 + key), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Delete(Key(kNKeys + keys[i / 10])));
|
|
|
|
env_->SleepForMicroseconds(5000);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t int_prop;
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.background-errors", &int_prop));
|
|
|
|
ASSERT_EQ(0U, int_prop);
|
|
|
|
|
|
|
|
// Verify DB
|
|
|
|
for (int j = 0; j < 2; j++) {
|
|
|
|
verify_func();
|
|
|
|
if (j == 0) {
|
|
|
|
Reopen(options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test compact range works
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
// All data should be in the last level.
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
db_->GetColumnFamilyMetaData(&cf_meta);
|
|
|
|
ASSERT_EQ(5U, cf_meta.levels.size());
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
ASSERT_EQ(0U, cf_meta.levels[i].files.size());
|
|
|
|
}
|
|
|
|
ASSERT_GT(cf_meta.levels[4U].files.size(), 0U);
|
|
|
|
verify_func();
|
|
|
|
|
|
|
|
Close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test specific cases in dynamic max bytes
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DynamicLevelMaxBytesBase2) {
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
Random rnd(301);
|
|
|
|
int kMaxKey = 1000000;
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_write_buffer_size = 2048;
|
|
|
|
options.write_buffer_size = 2048;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 9999;
|
|
|
|
options.level0_stop_writes_trigger = 9999;
|
|
|
|
options.target_file_size_base = 2048;
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
options.max_bytes_for_level_base = 10240;
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
|
|
|
options.max_background_compactions = 2;
|
|
|
|
options.num_levels = 5;
|
|
|
|
options.expanded_compaction_factor = 0; // Force not expanding in compactions
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.block_size = 1024;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "true"},
|
|
|
|
}));
|
|
|
|
|
|
|
|
uint64_t int_prop;
|
|
|
|
std::string str_prop;
|
|
|
|
|
|
|
|
// Initial base level is the last level
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(4U, int_prop);
|
|
|
|
|
|
|
|
// Put about 7K to L0
|
|
|
|
for (int i = 0; i < 70; i++) {
|
|
|
|
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
|
|
|
RandomString(&rnd, 80)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "false"},
|
|
|
|
}));
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(4U, int_prop);
|
|
|
|
|
|
|
|
// Insert extra about 3.5K to L0. After they are compacted to L4, base level
|
|
|
|
// should be changed to L3.
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "true"},
|
|
|
|
}));
|
|
|
|
for (int i = 0; i < 70; i++) {
|
|
|
|
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
|
|
|
RandomString(&rnd, 80)));
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "false"},
|
|
|
|
}));
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(3U, int_prop);
|
2015-05-14 00:26:02 +02:00
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level1", &str_prop));
|
|
|
|
ASSERT_EQ("0", str_prop);
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level2", &str_prop));
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
ASSERT_EQ("0", str_prop);
|
|
|
|
|
|
|
|
// Trigger parallel compaction, and the first one would change the base
|
|
|
|
// level.
|
|
|
|
// Hold compaction jobs to make sure
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
2015-03-14 16:21:53 +01:00
|
|
|
"CompactionJob::Run():Start",
|
2015-04-14 10:55:19 +02:00
|
|
|
[&](void* arg) { env_->SleepForMicroseconds(100000); });
|
options.level_compaction_dynamic_level_bytes to allow RocksDB to pick size bases of levels dynamically.
Summary:
When having fixed max_bytes_for_level_base, the ratio of size of largest level and the second one can range from 0 to the multiplier. This makes LSM tree frequently irregular and unpredictable. It can also cause poor space amplification in some cases.
In this improvement (proposed by Igor Kabiljo), we introduce a parameter option.level_compaction_use_dynamic_max_bytes. When turning it on, RocksDB is free to pick a level base in the range of (options.max_bytes_for_level_base/options.max_bytes_for_level_multiplier, options.max_bytes_for_level_base] so that real level ratios are close to options.max_bytes_for_level_multiplier.
Test Plan: New unit tests and pass tests suites including valgrind.
Reviewers: MarkCallaghan, rven, yhchiang, igor, ikabiljo
Reviewed By: ikabiljo
Subscribers: yoshinorim, ikabiljo, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D31437
2015-02-05 20:44:17 +01:00
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "true"},
|
|
|
|
}));
|
|
|
|
// Write about 10K more
|
|
|
|
for (int i = 0; i < 100; i++) {
|
|
|
|
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
|
|
|
RandomString(&rnd, 80)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "false"},
|
|
|
|
}));
|
|
|
|
Flush();
|
|
|
|
// Wait for 200 milliseconds before proceeding compactions to make sure two
|
|
|
|
// parallel ones are executed.
|
|
|
|
env_->SleepForMicroseconds(200000);
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(3U, int_prop);
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
|
|
|
|
// Trigger a condition that the compaction changes base level and L0->Lbase
|
|
|
|
// happens at the same time.
|
|
|
|
// We try to make last levels' targets to be 10K, 40K, 160K, add triggers
|
|
|
|
// another compaction from 40K->160K.
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "true"},
|
|
|
|
}));
|
|
|
|
// Write about 150K more
|
|
|
|
for (int i = 0; i < 1350; i++) {
|
|
|
|
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
|
|
|
RandomString(&rnd, 80)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "false"},
|
|
|
|
}));
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(2U, int_prop);
|
|
|
|
|
|
|
|
// Keep Writing data until base level changed 2->1. There will be L0->L2
|
|
|
|
// compaction going on at the same time.
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
for (int attempt = 0; attempt <= 20; attempt++) {
|
|
|
|
// Write about 5K more data with two flushes. It should be flush to level 2
|
|
|
|
// but when it is applied, base level is already 1.
|
|
|
|
for (int i = 0; i < 50; i++) {
|
|
|
|
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
|
|
|
RandomString(&rnd, 80)));
|
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
if (int_prop == 2U) {
|
|
|
|
env_->SleepForMicroseconds(50000);
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
|
|
|
|
|
|
|
env_->SleepForMicroseconds(200000);
|
|
|
|
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(1U, int_prop);
|
|
|
|
}
|
|
|
|
|
2015-04-15 06:45:20 +02:00
|
|
|
// Test specific cases in dynamic max bytes
|
|
|
|
TEST_F(DBTest, DynamicLevelMaxBytesCompactRange) {
|
|
|
|
Random rnd(301);
|
|
|
|
int kMaxKey = 1000000;
|
|
|
|
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_write_buffer_size = 2048;
|
|
|
|
options.write_buffer_size = 2048;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 9999;
|
|
|
|
options.level0_stop_writes_trigger = 9999;
|
|
|
|
options.target_file_size_base = 2;
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
options.max_bytes_for_level_base = 10240;
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
|
|
|
options.max_background_compactions = 1;
|
|
|
|
const int kNumLevels = 5;
|
|
|
|
options.num_levels = kNumLevels;
|
|
|
|
options.expanded_compaction_factor = 0; // Force not expanding in compactions
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.block_size = 1024;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// Compact against empty DB
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2015-04-15 06:45:20 +02:00
|
|
|
|
|
|
|
uint64_t int_prop;
|
|
|
|
std::string str_prop;
|
|
|
|
|
|
|
|
// Initial base level is the last level
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(4U, int_prop);
|
|
|
|
|
|
|
|
// Put about 7K to L0
|
|
|
|
for (int i = 0; i < 140; i++) {
|
|
|
|
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
|
|
|
RandomString(&rnd, 80)));
|
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
2015-05-18 20:47:16 +02:00
|
|
|
if (NumTableFilesAtLevel(0) == 0) {
|
|
|
|
// Make sure level 0 is not empty
|
|
|
|
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
|
|
|
RandomString(&rnd, 80)));
|
|
|
|
Flush();
|
|
|
|
}
|
2015-04-15 06:45:20 +02:00
|
|
|
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(3U, int_prop);
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level1", &str_prop));
|
|
|
|
ASSERT_EQ("0", str_prop);
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level2", &str_prop));
|
|
|
|
ASSERT_EQ("0", str_prop);
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
|
|
|
|
|
|
|
std::set<int> output_levels;
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"CompactionPicker::CompactRange:Return", [&](void* arg) {
|
|
|
|
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
|
|
|
output_levels.insert(compaction->output_level());
|
|
|
|
});
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2015-04-15 06:45:20 +02:00
|
|
|
ASSERT_EQ(output_levels.size(), 2);
|
|
|
|
ASSERT_TRUE(output_levels.find(3) != output_levels.end());
|
|
|
|
ASSERT_TRUE(output_levels.find(4) != output_levels.end());
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level0", &str_prop));
|
|
|
|
ASSERT_EQ("0", str_prop);
|
|
|
|
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level3", &str_prop));
|
|
|
|
ASSERT_EQ("0", str_prop);
|
|
|
|
// Base level is still level 3.
|
|
|
|
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
|
|
|
ASSERT_EQ(3U, int_prop);
|
|
|
|
}
|
|
|
|
|
2015-04-08 02:31:52 +02:00
|
|
|
TEST_F(DBTest, DynamicLevelMaxBytesBaseInc) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_write_buffer_size = 2048;
|
|
|
|
options.write_buffer_size = 2048;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 2;
|
|
|
|
options.target_file_size_base = 2048;
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
options.max_bytes_for_level_base = 10240;
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
2015-05-16 00:52:51 +02:00
|
|
|
options.soft_rate_limit = 1.1;
|
2015-04-08 02:31:52 +02:00
|
|
|
options.max_background_compactions = 2;
|
|
|
|
options.num_levels = 5;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
int non_trivial = 0;
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
2015-04-15 01:39:23 +02:00
|
|
|
"DBImpl::BackgroundCompaction:NonTrivial",
|
|
|
|
[&](void* arg) { non_trivial++; });
|
2015-04-08 02:31:52 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
Random rnd(301);
|
2015-04-15 04:58:52 +02:00
|
|
|
const int total_keys = 3000;
|
|
|
|
const int random_part_size = 100;
|
|
|
|
for (int i = 0; i < total_keys; i++) {
|
|
|
|
std::string value = RandomString(&rnd, random_part_size);
|
|
|
|
PutFixed32(&value, static_cast<uint32_t>(i));
|
|
|
|
ASSERT_OK(Put(Key(i), value));
|
2015-04-08 02:31:52 +02:00
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
|
|
|
|
ASSERT_EQ(non_trivial, 0);
|
|
|
|
|
2015-04-15 04:58:52 +02:00
|
|
|
for (int i = 0; i < total_keys; i++) {
|
|
|
|
std::string value = Get(Key(i));
|
|
|
|
ASSERT_EQ(DecodeFixed32(value.c_str() + random_part_size),
|
|
|
|
static_cast<uint32_t>(i));
|
|
|
|
}
|
|
|
|
|
2015-04-08 02:31:52 +02:00
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
|
|
|
}
|
|
|
|
|
2015-05-28 22:48:12 +02:00
|
|
|
TEST_F(DBTest, MigrateToDynamicLevelMaxBytesBase) {
|
|
|
|
Random rnd(301);
|
|
|
|
const int kMaxKey = 2000;
|
|
|
|
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_write_buffer_size = 2048;
|
|
|
|
options.write_buffer_size = 2048;
|
|
|
|
options.max_write_buffer_number = 8;
|
|
|
|
options.level0_file_num_compaction_trigger = 4;
|
|
|
|
options.level0_slowdown_writes_trigger = 4;
|
|
|
|
options.level0_stop_writes_trigger = 8;
|
|
|
|
options.target_file_size_base = 2048;
|
|
|
|
options.level_compaction_dynamic_level_bytes = false;
|
|
|
|
options.max_bytes_for_level_base = 10240;
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
2015-05-16 00:52:51 +02:00
|
|
|
options.soft_rate_limit = 1.1;
|
2015-05-28 22:48:12 +02:00
|
|
|
options.num_levels = 8;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
2015-06-03 20:57:26 +02:00
|
|
|
auto verify_func = [&](int num_keys, bool if_sleep) {
|
2015-05-28 22:48:12 +02:00
|
|
|
for (int i = 0; i < num_keys; i++) {
|
|
|
|
ASSERT_NE("NOT_FOUND", Get(Key(kMaxKey + i)));
|
|
|
|
if (i < num_keys / 10) {
|
|
|
|
ASSERT_EQ("NOT_FOUND", Get(Key(i)));
|
|
|
|
} else {
|
|
|
|
ASSERT_NE("NOT_FOUND", Get(Key(i)));
|
|
|
|
}
|
2015-06-03 20:57:26 +02:00
|
|
|
if (if_sleep && i % 1000 == 0) {
|
|
|
|
// Without it, valgrind may choose not to give another
|
|
|
|
// thread a chance to run before finishing the function,
|
|
|
|
// causing the test to be extremely slow.
|
|
|
|
env_->SleepForMicroseconds(1);
|
|
|
|
}
|
2015-05-28 22:48:12 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
int total_keys = 1000;
|
|
|
|
for (int i = 0; i < total_keys; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Delete(Key(i / 10)));
|
|
|
|
}
|
2015-06-03 20:57:26 +02:00
|
|
|
verify_func(total_keys, false);
|
2015-05-28 22:48:12 +02:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
Reopen(options);
|
2015-06-03 20:57:26 +02:00
|
|
|
verify_func(total_keys, false);
|
2015-05-28 22:48:12 +02:00
|
|
|
|
|
|
|
std::atomic_bool compaction_finished(false);
|
|
|
|
// Issue manual compaction in one thread and still verify DB state
|
|
|
|
// in main thread.
|
|
|
|
std::thread t([&]() {
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.change_level = true;
|
|
|
|
compact_options.target_level = options.num_levels - 1;
|
|
|
|
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
2015-05-28 22:48:12 +02:00
|
|
|
compaction_finished.store(true);
|
|
|
|
});
|
|
|
|
do {
|
2015-06-03 20:57:26 +02:00
|
|
|
verify_func(total_keys, true);
|
2015-05-28 22:48:12 +02:00
|
|
|
} while (!compaction_finished.load());
|
|
|
|
t.join();
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "false"},
|
|
|
|
}));
|
|
|
|
|
|
|
|
int total_keys2 = 2000;
|
|
|
|
for (int i = total_keys; i < total_keys2; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Delete(Key(i / 10)));
|
|
|
|
}
|
|
|
|
|
2015-06-03 20:57:26 +02:00
|
|
|
verify_func(total_keys2, false);
|
2015-05-28 22:48:12 +02:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
2015-06-03 20:57:26 +02:00
|
|
|
verify_func(total_keys2, false);
|
2015-05-28 22:48:12 +02:00
|
|
|
|
|
|
|
// Base level is not level 1
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
|
|
|
}
|
2015-04-08 02:31:52 +02:00
|
|
|
|
2015-06-04 04:57:01 +02:00
|
|
|
namespace {
|
|
|
|
class OnFileDeletionListener : public EventListener {
|
|
|
|
public:
|
|
|
|
OnFileDeletionListener() :
|
|
|
|
matched_count_(0),
|
|
|
|
expected_file_name_("") {}
|
|
|
|
|
|
|
|
void SetExpectedFileName(
|
|
|
|
const std::string file_name) {
|
|
|
|
expected_file_name_ = file_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VerifyMatchedCount(size_t expected_value) {
|
|
|
|
ASSERT_EQ(matched_count_, expected_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OnTableFileDeleted(
|
|
|
|
const TableFileDeletionInfo& info) override {
|
|
|
|
if (expected_file_name_ != "") {
|
|
|
|
ASSERT_EQ(expected_file_name_, info.file_path);
|
|
|
|
expected_file_name_ = "";
|
|
|
|
matched_count_++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
size_t matched_count_;
|
|
|
|
std::string expected_file_name_;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
2015-04-06 21:50:44 +02:00
|
|
|
if (!Snappy_Supported()) {
|
2015-03-13 19:18:35 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-03-10 20:35:15 +01:00
|
|
|
const int kNKeys = 120;
|
|
|
|
int keys[kNKeys];
|
|
|
|
for (int i = 0; i < kNKeys; i++) {
|
|
|
|
keys[i] = i;
|
|
|
|
}
|
|
|
|
std::random_shuffle(std::begin(keys), std::end(keys));
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_write_buffer_size = 20480;
|
|
|
|
options.write_buffer_size = 20480;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 2;
|
|
|
|
options.target_file_size_base = 2048;
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
options.max_bytes_for_level_base = 102400;
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
|
|
|
options.max_background_compactions = 1;
|
|
|
|
options.num_levels = 5;
|
|
|
|
|
|
|
|
options.compression_per_level.resize(3);
|
|
|
|
options.compression_per_level[0] = kNoCompression;
|
|
|
|
options.compression_per_level[1] = kNoCompression;
|
|
|
|
options.compression_per_level[2] = kSnappyCompression;
|
|
|
|
|
2015-06-04 04:57:01 +02:00
|
|
|
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
|
|
|
options.listeners.emplace_back(listener);
|
|
|
|
|
2015-03-10 20:35:15 +01:00
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// Insert more than 80K. L4 should be base level. Neither L0 nor L4 should
|
|
|
|
// be compressed, so total data size should be more than 80K.
|
|
|
|
for (int i = 0; i < 20; i++) {
|
|
|
|
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(3), 0);
|
|
|
|
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(4), 20U * 4000U);
|
|
|
|
|
|
|
|
// Insert 400KB. Some data will be compressed
|
|
|
|
for (int i = 21; i < 120; i++) {
|
|
|
|
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
|
|
|
ASSERT_LT(SizeAtLevel(0) + SizeAtLevel(3) + SizeAtLevel(4), 120U * 4000U);
|
|
|
|
// Make sure data in files in L3 is not compacted by removing all files
|
|
|
|
// in L4 and calculate number of rows
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"disable_auto_compactions", "true"},
|
|
|
|
}));
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
|
|
db_->GetColumnFamilyMetaData(&cf_meta);
|
|
|
|
for (auto file : cf_meta.levels[4].files) {
|
2015-06-04 04:57:01 +02:00
|
|
|
listener->SetExpectedFileName(dbname_ + file.name);
|
2015-03-10 20:35:15 +01:00
|
|
|
ASSERT_OK(dbfull()->DeleteFile(file.name));
|
|
|
|
}
|
2015-06-04 04:57:01 +02:00
|
|
|
listener->VerifyMatchedCount(cf_meta.levels[4].files.size());
|
|
|
|
|
2015-03-10 20:35:15 +01:00
|
|
|
int num_keys = 0;
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
num_keys++;
|
|
|
|
}
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(3), num_keys * 4000U);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DynamicLevelCompressionPerLevel2) {
|
2015-03-10 20:35:15 +01:00
|
|
|
const int kNKeys = 500;
|
|
|
|
int keys[kNKeys];
|
|
|
|
for (int i = 0; i < kNKeys; i++) {
|
|
|
|
keys[i] = i;
|
|
|
|
}
|
|
|
|
std::random_shuffle(std::begin(keys), std::end(keys));
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.db_write_buffer_size = 6000;
|
|
|
|
options.write_buffer_size = 6000;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 2;
|
2015-05-16 00:52:51 +02:00
|
|
|
options.soft_rate_limit = 1.1;
|
2015-03-10 20:35:15 +01:00
|
|
|
|
|
|
|
// Use file size to distinguish levels
|
|
|
|
// L1: 10, L2: 20, L3 40, L4 80
|
|
|
|
// L0 is less than 30
|
|
|
|
options.target_file_size_base = 10;
|
|
|
|
options.target_file_size_multiplier = 2;
|
|
|
|
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
options.max_bytes_for_level_base = 200;
|
|
|
|
options.max_bytes_for_level_multiplier = 8;
|
|
|
|
options.max_background_compactions = 1;
|
|
|
|
options.num_levels = 5;
|
|
|
|
std::shared_ptr<mock::MockTableFactory> mtf(new mock::MockTableFactory);
|
|
|
|
options.table_factory = mtf;
|
|
|
|
|
|
|
|
options.compression_per_level.resize(3);
|
|
|
|
options.compression_per_level[0] = kNoCompression;
|
|
|
|
options.compression_per_level[1] = kLZ4Compression;
|
|
|
|
options.compression_per_level[2] = kZlibCompression;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
// When base level is L4, L4 is LZ4.
|
2015-04-15 01:53:19 +02:00
|
|
|
std::atomic<int> num_zlib(0);
|
|
|
|
std::atomic<int> num_lz4(0);
|
|
|
|
std::atomic<int> num_no(0);
|
2015-04-14 10:55:19 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
|
|
|
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
|
|
|
if (compaction->output_level() == 4) {
|
|
|
|
ASSERT_TRUE(compaction->OutputCompressionType() == kLZ4Compression);
|
2015-04-15 01:53:19 +02:00
|
|
|
num_lz4.fetch_add(1);
|
2015-04-14 10:55:19 +02:00
|
|
|
}
|
|
|
|
});
|
2015-04-15 01:53:19 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"FlushJob::WriteLevel0Table:output_compression", [&](void* arg) {
|
|
|
|
auto* compression = reinterpret_cast<CompressionType*>(arg);
|
|
|
|
ASSERT_TRUE(*compression == kNoCompression);
|
|
|
|
num_no.fetch_add(1);
|
|
|
|
});
|
2015-04-14 10:55:19 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
2015-03-10 20:35:15 +01:00
|
|
|
for (int i = 0; i < 100; i++) {
|
|
|
|
ASSERT_OK(Put(Key(keys[i]), RandomString(&rnd, 200)));
|
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
2015-04-14 10:55:19 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
2015-03-10 20:35:15 +01:00
|
|
|
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(3), 0);
|
2015-04-14 10:55:19 +02:00
|
|
|
ASSERT_GT(NumTableFilesAtLevel(4), 0);
|
2015-04-15 01:53:19 +02:00
|
|
|
ASSERT_GT(num_no.load(), 2);
|
|
|
|
ASSERT_GT(num_lz4.load(), 0);
|
2015-04-14 10:55:19 +02:00
|
|
|
int prev_num_files_l4 = NumTableFilesAtLevel(4);
|
2015-03-10 20:35:15 +01:00
|
|
|
|
|
|
|
// After base level turn L4->L3, L3 becomes LZ4 and L4 becomes Zlib
|
2015-04-15 01:53:19 +02:00
|
|
|
num_lz4.store(0);
|
|
|
|
num_no.store(0);
|
2015-04-14 10:55:19 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
|
|
|
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
|
|
|
if (compaction->output_level() == 4 && compaction->start_level() == 3) {
|
|
|
|
ASSERT_TRUE(compaction->OutputCompressionType() == kZlibCompression);
|
2015-04-15 01:53:19 +02:00
|
|
|
num_zlib.fetch_add(1);
|
2015-04-14 10:55:19 +02:00
|
|
|
} else {
|
|
|
|
ASSERT_TRUE(compaction->OutputCompressionType() == kLZ4Compression);
|
2015-04-15 01:53:19 +02:00
|
|
|
num_lz4.fetch_add(1);
|
2015-04-14 10:55:19 +02:00
|
|
|
}
|
|
|
|
});
|
2015-04-15 01:53:19 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"FlushJob::WriteLevel0Table:output_compression", [&](void* arg) {
|
|
|
|
auto* compression = reinterpret_cast<CompressionType*>(arg);
|
|
|
|
ASSERT_TRUE(*compression == kNoCompression);
|
|
|
|
num_no.fetch_add(1);
|
|
|
|
});
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
2015-04-14 10:55:19 +02:00
|
|
|
|
2015-03-10 20:35:15 +01:00
|
|
|
for (int i = 101; i < 500; i++) {
|
|
|
|
ASSERT_OK(Put(Key(keys[i]), RandomString(&rnd, 200)));
|
|
|
|
if (i % 100 == 99) {
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
|
|
|
}
|
2015-04-14 10:55:19 +02:00
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
2015-03-10 20:35:15 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
2015-04-14 10:55:19 +02:00
|
|
|
ASSERT_GT(NumTableFilesAtLevel(3), 0);
|
|
|
|
ASSERT_GT(NumTableFilesAtLevel(4), prev_num_files_l4);
|
2015-04-15 01:53:19 +02:00
|
|
|
ASSERT_GT(num_no.load(), 2);
|
|
|
|
ASSERT_GT(num_lz4.load(), 0);
|
|
|
|
ASSERT_GT(num_zlib.load(), 0);
|
2015-03-10 20:35:15 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DynamicCompactionOptions) {
|
2014-10-17 02:21:31 +02:00
|
|
|
// minimum write buffer size is enforced at 64KB
|
|
|
|
const uint64_t k32KB = 1 << 15;
|
2014-10-02 01:19:16 +02:00
|
|
|
const uint64_t k64KB = 1 << 16;
|
|
|
|
const uint64_t k128KB = 1 << 17;
|
2014-10-30 01:02:21 +01:00
|
|
|
const uint64_t k1MB = 1 << 20;
|
2014-10-17 02:21:31 +02:00
|
|
|
const uint64_t k4KB = 1 << 12;
|
2014-10-02 01:19:16 +02:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.compression = kNoCompression;
|
2015-05-16 00:52:51 +02:00
|
|
|
options.soft_rate_limit = 1.1;
|
2014-10-17 02:21:31 +02:00
|
|
|
options.write_buffer_size = k64KB;
|
2014-10-02 01:19:16 +02:00
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
// Compaction related options
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
2014-10-17 02:14:17 +02:00
|
|
|
options.level0_slowdown_writes_trigger = 4;
|
|
|
|
options.level0_stop_writes_trigger = 8;
|
2014-10-02 01:19:16 +02:00
|
|
|
options.max_grandparent_overlap_factor = 10;
|
|
|
|
options.expanded_compaction_factor = 25;
|
|
|
|
options.source_compaction_factor = 1;
|
2014-10-17 02:21:31 +02:00
|
|
|
options.target_file_size_base = k64KB;
|
2014-10-02 01:19:16 +02:00
|
|
|
options.target_file_size_multiplier = 1;
|
2014-10-17 02:21:31 +02:00
|
|
|
options.max_bytes_for_level_base = k128KB;
|
2014-10-02 01:19:16 +02:00
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
2014-10-17 02:14:17 +02:00
|
|
|
|
|
|
|
// Block flush thread and disable compaction thread
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-02 01:19:16 +02:00
|
|
|
|
2014-10-17 23:46:40 +02:00
|
|
|
auto gen_l0_kb = [this](int start, int size, int stride) {
|
2014-10-02 01:19:16 +02:00
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < size; i++) {
|
2014-10-17 01:57:59 +02:00
|
|
|
ASSERT_OK(Put(Key(start + stride * i), RandomString(&rnd, 1024)));
|
2014-10-02 01:19:16 +02:00
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
};
|
|
|
|
|
2014-10-24 00:35:10 +02:00
|
|
|
// Write 3 files that have the same key range.
|
|
|
|
// Since level0_file_num_compaction_trigger is 3, compaction should be
|
|
|
|
// triggered. The compaction should result in one L1 file
|
2014-10-17 23:46:40 +02:00
|
|
|
gen_l0_kb(0, 64, 1);
|
2014-10-02 01:19:16 +02:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
2014-10-17 23:46:40 +02:00
|
|
|
gen_l0_kb(0, 64, 1);
|
2014-10-02 01:19:16 +02:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 2);
|
2014-10-17 23:46:40 +02:00
|
|
|
gen_l0_kb(0, 64, 1);
|
2014-10-02 01:19:16 +02:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel());
|
|
|
|
std::vector<LiveFileMetaData> metadata;
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
2014-10-02 10:05:59 +02:00
|
|
|
ASSERT_EQ(1U, metadata.size());
|
2014-10-17 02:21:31 +02:00
|
|
|
ASSERT_LE(metadata[0].size, k64KB + k4KB);
|
|
|
|
ASSERT_GE(metadata[0].size, k64KB - k4KB);
|
2014-10-02 01:19:16 +02:00
|
|
|
|
2014-10-17 02:21:31 +02:00
|
|
|
// Test compaction trigger and target_file_size_base
|
2014-10-24 00:35:10 +02:00
|
|
|
// Reduce compaction trigger to 2, and reduce L1 file size to 32KB.
|
|
|
|
// Writing to 64KB L0 files should trigger a compaction. Since these
|
|
|
|
// 2 L0 files have the same key range, compaction merge them and should
|
|
|
|
// result in 2 32KB L1 files.
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-02 01:19:16 +02:00
|
|
|
{"level0_file_num_compaction_trigger", "2"},
|
2014-11-25 05:44:49 +01:00
|
|
|
{"target_file_size_base", ToString(k32KB) }
|
2014-10-02 01:19:16 +02:00
|
|
|
}));
|
|
|
|
|
2014-10-17 23:46:40 +02:00
|
|
|
gen_l0_kb(0, 64, 1);
|
2014-10-02 01:19:16 +02:00
|
|
|
ASSERT_EQ("1,1", FilesPerLevel());
|
2014-10-17 23:46:40 +02:00
|
|
|
gen_l0_kb(0, 64, 1);
|
2014-10-02 01:19:16 +02:00
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ("0,2", FilesPerLevel());
|
|
|
|
metadata.clear();
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
2014-10-02 10:05:59 +02:00
|
|
|
ASSERT_EQ(2U, metadata.size());
|
2014-10-17 02:21:31 +02:00
|
|
|
ASSERT_LE(metadata[0].size, k32KB + k4KB);
|
|
|
|
ASSERT_GE(metadata[0].size, k32KB - k4KB);
|
2014-10-24 00:35:10 +02:00
|
|
|
ASSERT_LE(metadata[1].size, k32KB + k4KB);
|
|
|
|
ASSERT_GE(metadata[1].size, k32KB - k4KB);
|
2014-10-02 01:19:16 +02:00
|
|
|
|
2014-10-17 02:21:31 +02:00
|
|
|
// Test max_bytes_for_level_base
|
2014-10-24 00:35:10 +02:00
|
|
|
// Increase level base size to 256KB and write enough data that will
|
|
|
|
// fill L1 and L2. L1 size should be around 256KB while L2 size should be
|
|
|
|
// around 256KB x 4.
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-11-25 05:44:49 +01:00
|
|
|
{"max_bytes_for_level_base", ToString(k1MB) }
|
2014-10-17 02:21:31 +02:00
|
|
|
}));
|
2014-10-02 01:19:16 +02:00
|
|
|
|
2014-10-30 01:02:21 +01:00
|
|
|
// writing 96 x 64KB => 6 * 1024KB
|
|
|
|
// (L1 + L2) = (1 + 4) * 1024KB
|
|
|
|
for (int i = 0; i < 96; ++i) {
|
|
|
|
gen_l0_kb(i, 64, 96);
|
2014-10-02 01:19:16 +02:00
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
2014-11-04 20:33:57 +01:00
|
|
|
ASSERT_GT(SizeAtLevel(1), k1MB / 2);
|
|
|
|
ASSERT_LT(SizeAtLevel(1), k1MB + k1MB / 2);
|
|
|
|
|
|
|
|
// Within (0.5, 1.5) of 4MB.
|
|
|
|
ASSERT_GT(SizeAtLevel(2), 2 * k1MB);
|
|
|
|
ASSERT_LT(SizeAtLevel(2), 6 * k1MB);
|
2014-10-02 01:19:16 +02:00
|
|
|
|
2014-10-17 02:21:31 +02:00
|
|
|
// Test max_bytes_for_level_multiplier and
|
2014-10-24 00:35:10 +02:00
|
|
|
// max_bytes_for_level_base. Now, reduce both mulitplier and level base,
|
|
|
|
// After filling enough data that can fit in L1 - L3, we should see L1 size
|
|
|
|
// reduces to 128KB from 256KB which was asserted previously. Same for L2.
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-02 01:19:16 +02:00
|
|
|
{"max_bytes_for_level_multiplier", "2"},
|
2014-11-25 05:44:49 +01:00
|
|
|
{"max_bytes_for_level_base", ToString(k128KB) }
|
2014-10-02 01:19:16 +02:00
|
|
|
}));
|
|
|
|
|
2014-10-17 02:21:31 +02:00
|
|
|
// writing 20 x 64KB = 10 x 128KB
|
|
|
|
// (L1 + L2 + L3) = (1 + 2 + 4) * 128KB
|
|
|
|
for (int i = 0; i < 20; ++i) {
|
|
|
|
gen_l0_kb(i, 64, 32);
|
2014-10-02 01:19:16 +02:00
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
2014-10-30 01:02:21 +01:00
|
|
|
uint64_t total_size =
|
|
|
|
SizeAtLevel(1) + SizeAtLevel(2) + SizeAtLevel(3);
|
|
|
|
ASSERT_TRUE(total_size < k128KB * 7 * 1.5);
|
2014-10-17 02:14:17 +02:00
|
|
|
|
2014-10-24 00:35:10 +02:00
|
|
|
// Test level0_stop_writes_trigger.
|
|
|
|
// Clean up memtable and L0. Block compaction threads. If continue to write
|
|
|
|
// and flush memtables. We should see put timeout after 8 memtable flushes
|
|
|
|
// since level0_stop_writes_trigger = 8
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-10-17 02:14:17 +02:00
|
|
|
// Block compaction
|
|
|
|
SleepingBackgroundTask sleeping_task_low1;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low1,
|
|
|
|
Env::Priority::LOW);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
int count = 0;
|
|
|
|
Random rnd(301);
|
|
|
|
WriteOptions wo;
|
|
|
|
wo.timeout_hint_us = 10000;
|
|
|
|
while (Put(Key(count), RandomString(&rnd, 1024), wo).ok() && count < 64) {
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
count++;
|
|
|
|
}
|
2014-10-17 02:21:31 +02:00
|
|
|
// Stop trigger = 8
|
2014-10-17 02:14:17 +02:00
|
|
|
ASSERT_EQ(count, 8);
|
|
|
|
// Unblock
|
|
|
|
sleeping_task_low1.WakeUp();
|
|
|
|
sleeping_task_low1.WaitUntilDone();
|
|
|
|
|
2014-10-24 00:35:10 +02:00
|
|
|
// Now reduce level0_stop_writes_trigger to 6. Clear up memtables and L0.
|
|
|
|
// Block compaction thread again. Perform the put and memtable flushes
|
|
|
|
// until we see timeout after 6 memtable flushes.
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-17 02:14:17 +02:00
|
|
|
{"level0_stop_writes_trigger", "6"}
|
|
|
|
}));
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-10-17 02:14:17 +02:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
|
|
|
|
// Block compaction
|
|
|
|
SleepingBackgroundTask sleeping_task_low2;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low2,
|
|
|
|
Env::Priority::LOW);
|
|
|
|
count = 0;
|
|
|
|
while (Put(Key(count), RandomString(&rnd, 1024), wo).ok() && count < 64) {
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(count, 6);
|
|
|
|
// Unblock
|
|
|
|
sleeping_task_low2.WakeUp();
|
|
|
|
sleeping_task_low2.WaitUntilDone();
|
|
|
|
|
|
|
|
// Test disable_auto_compactions
|
2014-10-24 00:35:10 +02:00
|
|
|
// Compaction thread is unblocked but auto compaction is disabled. Write
|
|
|
|
// 4 L0 files and compaction should be triggered. If auto compaction is
|
|
|
|
// disabled, then TEST_WaitForCompact will be waiting for nothing. Number of
|
|
|
|
// L0 files do not change after the call.
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-17 02:14:17 +02:00
|
|
|
{"disable_auto_compactions", "true"}
|
|
|
|
}));
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-10-17 02:14:17 +02:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
|
|
|
// Wait for compaction so that put won't timeout
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 4);
|
|
|
|
|
2014-10-24 00:35:10 +02:00
|
|
|
// Enable auto compaction and perform the same test, # of L0 files should be
|
|
|
|
// reduced after compaction.
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-17 02:14:17 +02:00
|
|
|
{"disable_auto_compactions", "false"}
|
|
|
|
}));
|
2015-06-17 23:36:14 +02:00
|
|
|
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2014-10-17 02:14:17 +02:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
|
|
|
// Wait for compaction so that put won't timeout
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_LT(NumTableFilesAtLevel(0), 4);
|
2014-10-17 02:21:31 +02:00
|
|
|
|
2014-10-24 00:37:14 +02:00
|
|
|
// Test max_mem_compaction_level.
|
2015-04-25 11:14:27 +02:00
|
|
|
// Destroy DB and start from scratch
|
2014-10-24 00:37:14 +02:00
|
|
|
options.max_background_compactions = 1;
|
|
|
|
options.max_background_flushes = 0;
|
|
|
|
options.max_mem_compaction_level = 2;
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-24 00:37:14 +02:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_OK(Put("max_mem_compaction_level_key", RandomString(&rnd, 8)));
|
2014-10-24 00:37:14 +02:00
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 1);
|
|
|
|
|
|
|
|
ASSERT_TRUE(Put("max_mem_compaction_level_key",
|
|
|
|
RandomString(&rnd, 8)).ok());
|
|
|
|
// Set new value and it becomes effective in this flush
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-24 00:37:14 +02:00
|
|
|
{"max_mem_compaction_level", "1"}
|
|
|
|
}));
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 1);
|
|
|
|
|
|
|
|
ASSERT_TRUE(Put("max_mem_compaction_level_key",
|
|
|
|
RandomString(&rnd, 8)).ok());
|
|
|
|
// Set new value and it becomes effective in this flush
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-24 00:37:14 +02:00
|
|
|
{"max_mem_compaction_level", "0"}
|
|
|
|
}));
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2), 1);
|
2014-10-02 01:19:16 +02:00
|
|
|
}
|
2014-09-06 00:20:05 +02:00
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, FileCreationRandomFailure) {
|
2014-10-28 22:27:26 +01:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
|
|
|
options.target_file_size_base = 200000;
|
|
|
|
options.max_bytes_for_level_base = 1000000;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-28 22:27:26 +01:00
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
const int kTestSize = kCDTKeysPerBuffer * 4096;
|
|
|
|
const int kTotalIteration = 100;
|
|
|
|
// the second half of the test involves in random failure
|
|
|
|
// of file creation.
|
|
|
|
const int kRandomFailureTest = kTotalIteration / 2;
|
|
|
|
std::vector<std::string> values;
|
|
|
|
for (int i = 0; i < kTestSize; ++i) {
|
|
|
|
values.push_back("NOT_FOUND");
|
|
|
|
}
|
|
|
|
for (int j = 0; j < kTotalIteration; ++j) {
|
|
|
|
if (j == kRandomFailureTest) {
|
|
|
|
env_->non_writeable_rate_.store(90);
|
|
|
|
}
|
|
|
|
for (int k = 0; k < kTestSize; ++k) {
|
|
|
|
// here we expect some of the Put fails.
|
|
|
|
std::string value = RandomString(&rnd, 100);
|
|
|
|
Status s = Put(Key(k), Slice(value));
|
|
|
|
if (s.ok()) {
|
|
|
|
// update the latest successful put
|
|
|
|
values[k] = value;
|
|
|
|
}
|
|
|
|
// But everything before we simulate the failure-test should succeed.
|
|
|
|
if (j < kRandomFailureTest) {
|
|
|
|
ASSERT_OK(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If rocksdb does not do the correct job, internal assert will fail here.
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
// verify we have the latest successful update
|
|
|
|
for (int k = 0; k < kTestSize; ++k) {
|
|
|
|
auto v = Get(Key(k));
|
|
|
|
ASSERT_EQ(v, values[k]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// reopen and reverify we have the latest successful update
|
|
|
|
env_->non_writeable_rate_.store(0);
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-10-28 22:27:26 +01:00
|
|
|
for (int k = 0; k < kTestSize; ++k) {
|
|
|
|
auto v = Get(Key(k));
|
|
|
|
ASSERT_EQ(v, values[k]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, PartialCompactionFailure) {
|
2014-10-28 22:27:26 +01:00
|
|
|
Options options;
|
|
|
|
const int kKeySize = 16;
|
|
|
|
const int kKvSize = 1000;
|
|
|
|
const int kKeysPerBuffer = 100;
|
|
|
|
const int kNumL1Files = 5;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = kKeysPerBuffer * kKvSize;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.target_file_size_base =
|
|
|
|
options.write_buffer_size *
|
|
|
|
(options.max_write_buffer_number - 1);
|
|
|
|
options.level0_file_num_compaction_trigger = kNumL1Files;
|
|
|
|
options.max_bytes_for_level_base =
|
|
|
|
options.level0_file_num_compaction_trigger *
|
|
|
|
options.target_file_size_base;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
|
2014-11-07 02:07:52 +01:00
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
// stop the compaction thread until we simulate the file creation failure.
|
|
|
|
SleepingBackgroundTask sleeping_task_low;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
|
|
|
Env::Priority::LOW);
|
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
options.env = env_;
|
|
|
|
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-28 22:27:26 +01:00
|
|
|
|
2014-10-31 19:59:54 +01:00
|
|
|
const int kNumInsertedKeys =
|
2014-10-28 22:27:26 +01:00
|
|
|
options.level0_file_num_compaction_trigger *
|
|
|
|
(options.max_write_buffer_number - 1) *
|
2014-10-28 23:35:10 +01:00
|
|
|
kKeysPerBuffer;
|
2014-10-28 22:27:26 +01:00
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
std::vector<std::string> keys;
|
|
|
|
std::vector<std::string> values;
|
2014-10-31 19:59:54 +01:00
|
|
|
for (int k = 0; k < kNumInsertedKeys; ++k) {
|
2014-10-28 22:27:26 +01:00
|
|
|
keys.emplace_back(RandomString(&rnd, kKeySize));
|
|
|
|
values.emplace_back(RandomString(&rnd, kKvSize - kKeySize));
|
|
|
|
ASSERT_OK(Put(Slice(keys[k]), Slice(values[k])));
|
|
|
|
}
|
|
|
|
|
2014-11-07 02:07:52 +01:00
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
// Make sure the number of L0 files can trigger compaction.
|
|
|
|
ASSERT_GE(NumTableFilesAtLevel(0),
|
|
|
|
options.level0_file_num_compaction_trigger);
|
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
auto previous_num_level0_files = NumTableFilesAtLevel(0);
|
2014-10-28 23:35:10 +01:00
|
|
|
|
2014-11-07 02:07:52 +01:00
|
|
|
// Fail the first file creation.
|
|
|
|
env_->non_writable_count_ = 1;
|
|
|
|
sleeping_task_low.WakeUp();
|
|
|
|
sleeping_task_low.WaitUntilDone();
|
2014-10-28 23:35:10 +01:00
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
// Expect compaction to fail here as one file will fail its
|
|
|
|
// creation.
|
2014-11-07 02:07:52 +01:00
|
|
|
ASSERT_TRUE(!dbfull()->TEST_WaitForCompact().ok());
|
2014-11-06 22:53:02 +01:00
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
// Verify L0 -> L1 compaction does fail.
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
2014-11-06 22:53:02 +01:00
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
// Verify all L0 files are still there.
|
2014-11-07 02:07:52 +01:00
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), previous_num_level0_files);
|
2014-10-28 22:27:26 +01:00
|
|
|
|
|
|
|
// All key-values must exist after compaction fails.
|
2014-10-31 19:59:54 +01:00
|
|
|
for (int k = 0; k < kNumInsertedKeys; ++k) {
|
2014-10-28 22:27:26 +01:00
|
|
|
ASSERT_EQ(values[k], Get(keys[k]));
|
|
|
|
}
|
|
|
|
|
2014-11-07 02:07:52 +01:00
|
|
|
env_->non_writable_count_ = 0;
|
2014-10-28 23:35:10 +01:00
|
|
|
|
2014-10-28 22:27:26 +01:00
|
|
|
// Make sure RocksDB will not get into corrupted state.
|
DBTest: options clean up - part 1
Summary:
DBTest has several functions (Reopen(), TryReopen(), ChangeOptins(), etc
that takes a pointer to options), depending on if it is nullptr, it uses
different options underneath. This makes it really hard to track what
options is used in different test case. We should just kill the default
value and make it being passed into explicitly. It is going to be very
hairy. I will start with simple ones.
Test Plan:
make db_test
stacked diffs, will run test with full stack
Reviewers: sdong, yhchiang, rven, igor
Reviewed By: igor
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D27687
2014-10-29 19:58:09 +01:00
|
|
|
Reopen(options);
|
2014-10-28 22:27:26 +01:00
|
|
|
|
|
|
|
// Verify again after reopen.
|
2014-10-31 19:59:54 +01:00
|
|
|
for (int k = 0; k < kNumInsertedKeys; ++k) {
|
2014-10-28 22:27:26 +01:00
|
|
|
ASSERT_EQ(values[k], Get(keys[k]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DynamicMiscOptions) {
|
2014-10-24 00:34:21 +02:00
|
|
|
// Test max_sequential_skip_in_iterations
|
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.max_sequential_skip_in_iterations = 16;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
2014-10-29 19:59:18 +01:00
|
|
|
DestroyAndReopen(options);
|
2014-10-24 00:34:21 +02:00
|
|
|
|
|
|
|
auto assert_reseek_count = [this, &options](int key_start, int num_reseek) {
|
|
|
|
int key0 = key_start;
|
|
|
|
int key1 = key_start + 1;
|
|
|
|
int key2 = key_start + 2;
|
|
|
|
Random rnd(301);
|
|
|
|
ASSERT_OK(Put(Key(key0), RandomString(&rnd, 8)));
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
|
|
|
ASSERT_OK(Put(Key(key1), RandomString(&rnd, 8)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Put(Key(key2), RandomString(&rnd, 8)));
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
|
|
|
iter->Seek(Key(key1));
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Key(key1)), 0);
|
|
|
|
iter->Next();
|
|
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
ASSERT_EQ(iter->key().compare(Key(key2)), 0);
|
|
|
|
ASSERT_EQ(num_reseek,
|
|
|
|
TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION));
|
|
|
|
};
|
|
|
|
// No reseek
|
|
|
|
assert_reseek_count(100, 0);
|
|
|
|
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-24 00:34:21 +02:00
|
|
|
{"max_sequential_skip_in_iterations", "4"}
|
|
|
|
}));
|
|
|
|
// Clear memtable and make new option effective
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
// Trigger reseek
|
|
|
|
assert_reseek_count(200, 1);
|
|
|
|
|
2014-11-05 01:23:05 +01:00
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
2014-10-24 00:34:21 +02:00
|
|
|
{"max_sequential_skip_in_iterations", "16"}
|
|
|
|
}));
|
|
|
|
// Clear memtable and make new option effective
|
|
|
|
dbfull()->TEST_FlushMemTable(true);
|
|
|
|
// No reseek
|
|
|
|
assert_reseek_count(300, 1);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DontDeletePendingOutputs) {
|
2014-11-07 20:50:34 +01:00
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// Every time we write to a table file, call FOF/POF with full DB scan. This
|
|
|
|
// will make sure our pending_outputs_ protection work correctly
|
|
|
|
std::function<void()> purge_obsolete_files_function = [&]() {
|
2015-02-12 18:54:48 +01:00
|
|
|
JobContext job_context(0);
|
2014-11-07 20:50:34 +01:00
|
|
|
dbfull()->TEST_LockMutex();
|
|
|
|
dbfull()->FindObsoleteFiles(&job_context, true /*force*/);
|
|
|
|
dbfull()->TEST_UnlockMutex();
|
|
|
|
dbfull()->PurgeObsoleteFiles(job_context);
|
|
|
|
};
|
|
|
|
|
|
|
|
env_->table_write_callback_ = &purge_obsolete_files_function;
|
|
|
|
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
ASSERT_OK(Put("a", "begin"));
|
|
|
|
ASSERT_OK(Put("z", "end"));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
|
|
|
|
// If pending output guard does not work correctly, PurgeObsoleteFiles() will
|
|
|
|
// delete the file that Compaction is trying to create, causing this: error
|
|
|
|
// db/db_test.cc:975: IO error:
|
|
|
|
// /tmp/rocksdbtest-1552237650/db_test/000009.sst: No such file or directory
|
|
|
|
Compact("a", "b");
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DontDeleteMovedFile) {
|
2014-12-22 12:04:45 +01:00
|
|
|
// This test triggers move compaction and verifies that the file is not
|
|
|
|
// deleted when it's part of move compaction
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.max_bytes_for_level_base = 1024 * 1024; // 1 MB
|
|
|
|
options.level0_file_num_compaction_trigger =
|
|
|
|
2; // trigger compaction when we have 2 files
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
// Create two 1MB sst files
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
// Create 1MB sst file
|
|
|
|
for (int j = 0; j < 100; ++j) {
|
|
|
|
ASSERT_OK(Put(Key(i * 50 + j), RandomString(&rnd, 10 * 1024)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
// this should execute both L0->L1 and L1->(move)->L2 compactions
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ("0,0,1", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// If the moved file is actually deleted (the move-safeguard in
|
|
|
|
// ~Version::Version() is not there), we get this failure:
|
|
|
|
// Corruption: Can't access /000009.sst
|
|
|
|
Reopen(options);
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DeleteMovedFileAfterCompaction) {
|
2015-02-06 17:44:30 +01:00
|
|
|
// iter 1 -- delete_obsolete_files_period_micros == 0
|
|
|
|
for (int iter = 0; iter < 2; ++iter) {
|
|
|
|
// This test triggers move compaction and verifies that the file is not
|
|
|
|
// deleted when it's part of move compaction
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
if (iter == 1) {
|
|
|
|
options.delete_obsolete_files_period_micros = 0;
|
|
|
|
}
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.level0_file_num_compaction_trigger =
|
|
|
|
2; // trigger compaction when we have 2 files
|
2015-06-04 04:57:01 +02:00
|
|
|
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
|
|
|
options.listeners.emplace_back(listener);
|
2015-02-06 17:44:30 +01:00
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
// Create two 1MB sst files
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
// Create 1MB sst file
|
|
|
|
for (int j = 0; j < 100; ++j) {
|
|
|
|
ASSERT_OK(Put(Key(i * 50 + j), RandomString(&rnd, 10 * 1024)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
// this should execute L0->L1
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// block compactions
|
|
|
|
SleepingBackgroundTask sleeping_task;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task,
|
|
|
|
Env::Priority::LOW);
|
|
|
|
|
|
|
|
options.max_bytes_for_level_base = 1024 * 1024; // 1 MB
|
|
|
|
Reopen(options);
|
|
|
|
std::unique_ptr<Iterator> iterator(db_->NewIterator(ReadOptions()));
|
|
|
|
ASSERT_EQ("0,1", FilesPerLevel(0));
|
|
|
|
// let compactions go
|
|
|
|
sleeping_task.WakeUp();
|
|
|
|
sleeping_task.WaitUntilDone();
|
|
|
|
|
|
|
|
// this should execute L1->L2 (move)
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
ASSERT_EQ("0,0,1", FilesPerLevel(0));
|
|
|
|
|
|
|
|
std::vector<LiveFileMetaData> metadata;
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
|
|
|
ASSERT_EQ(metadata.size(), 1U);
|
|
|
|
auto moved_file_name = metadata[0].name;
|
|
|
|
|
|
|
|
// Create two more 1MB sst files
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
// Create 1MB sst file
|
|
|
|
for (int j = 0; j < 100; ++j) {
|
|
|
|
ASSERT_OK(Put(Key(i * 50 + j + 100), RandomString(&rnd, 10 * 1024)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
// this should execute both L0->L1 and L1->L2 (merge with previous file)
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
ASSERT_EQ("0,0,2", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// iterator is holding the file
|
2015-06-04 04:57:01 +02:00
|
|
|
ASSERT_TRUE(env_->FileExists(dbname_ + moved_file_name));
|
2015-02-06 17:44:30 +01:00
|
|
|
|
2015-06-04 04:57:01 +02:00
|
|
|
listener->SetExpectedFileName(dbname_ + moved_file_name);
|
2015-02-06 17:44:30 +01:00
|
|
|
iterator.reset();
|
|
|
|
|
|
|
|
// this file should have been compacted away
|
2015-06-04 04:57:01 +02:00
|
|
|
ASSERT_TRUE(!env_->FileExists(dbname_ + moved_file_name));
|
|
|
|
listener->VerifyMatchedCount(1);
|
2015-02-06 17:44:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, OptimizeFiltersForHits) {
|
2015-02-17 17:03:45 +01:00
|
|
|
Options options = CurrentOptions();
|
2015-03-11 01:53:22 +01:00
|
|
|
options.write_buffer_size = 256 * 1024;
|
|
|
|
options.target_file_size_base = 256 * 1024;
|
2015-02-17 17:03:45 +01:00
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 4;
|
2015-03-11 01:53:22 +01:00
|
|
|
options.max_bytes_for_level_base = 256 * 1024;
|
2015-02-17 17:03:45 +01:00
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.max_background_compactions = 8;
|
|
|
|
options.max_background_flushes = 8;
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
BlockBasedTableOptions bbto;
|
|
|
|
bbto.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
|
|
|
bbto.whole_key_filtering = true;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
|
|
|
options.optimize_filters_for_hits = true;
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
CreateAndReopenWithCF({"mypikachu"}, options);
|
|
|
|
|
|
|
|
int numkeys = 200000;
|
|
|
|
for (int i = 0; i < 20; i += 2) {
|
|
|
|
for (int j = i; j < numkeys; j += 20) {
|
|
|
|
ASSERT_OK(Put(1, Key(j), "val"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
for (int i = 1; i < numkeys; i += 2) {
|
|
|
|
ASSERT_EQ(Get(1, Key(i)), "NOT_FOUND");
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L0));
|
|
|
|
ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L1));
|
|
|
|
ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L2_AND_UP));
|
|
|
|
|
|
|
|
// When the skip_filters_on_last_level is ON, the last level which has
|
|
|
|
// most of the keys does not use bloom filters. We end up using
|
|
|
|
// bloom filters in a very small number of cases. Without the flag.
|
|
|
|
// this number would be close to 150000 (all the key at the last level) +
|
|
|
|
// some use in the upper levels
|
|
|
|
//
|
|
|
|
ASSERT_GT(90000, TestGetTickerCount(options, BLOOM_FILTER_USEFUL));
|
|
|
|
|
|
|
|
for (int i = 0; i < numkeys; i += 2) {
|
|
|
|
ASSERT_EQ(Get(1, Key(i)), "val");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, L0L1L2AndUpHitCounter) {
|
2015-02-09 23:53:58 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.write_buffer_size = 32 * 1024;
|
|
|
|
options.target_file_size_base = 32 * 1024;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 4;
|
|
|
|
options.max_bytes_for_level_base = 64 * 1024;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.max_background_compactions = 8;
|
|
|
|
options.max_background_flushes = 8;
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
CreateAndReopenWithCF({"mypikachu"}, options);
|
|
|
|
|
|
|
|
int numkeys = 20000;
|
|
|
|
for (int i = 0; i < numkeys; i++) {
|
|
|
|
ASSERT_OK(Put(1, Key(i), "val"));
|
|
|
|
}
|
|
|
|
ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L0));
|
|
|
|
ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L1));
|
|
|
|
ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L2_AND_UP));
|
|
|
|
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
for (int i = 0; i < numkeys; i++) {
|
|
|
|
ASSERT_EQ(Get(1, Key(i)), "val");
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, GET_HIT_L0), 100);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, GET_HIT_L1), 100);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, GET_HIT_L2_AND_UP), 100);
|
|
|
|
|
|
|
|
ASSERT_EQ(numkeys, TestGetTickerCount(options, GET_HIT_L0) +
|
|
|
|
TestGetTickerCount(options, GET_HIT_L1) +
|
|
|
|
TestGetTickerCount(options, GET_HIT_L2_AND_UP));
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
2015-01-15 01:24:24 +01:00
|
|
|
// iter 0 -- zlib
|
|
|
|
// iter 1 -- bzip2
|
|
|
|
// iter 2 -- lz4
|
|
|
|
// iter 3 -- lz4HC
|
|
|
|
CompressionType compressions[] = {kZlibCompression, kBZip2Compression,
|
|
|
|
kLZ4Compression, kLZ4HCCompression};
|
|
|
|
for (int iter = 0; iter < 4; ++iter) {
|
|
|
|
// first_table_version 1 -- generate with table_version == 1, read with
|
|
|
|
// table_version == 2
|
|
|
|
// first_table_version 2 -- generate with table_version == 2, read with
|
|
|
|
// table_version == 1
|
|
|
|
for (int first_table_version = 1; first_table_version <= 2;
|
|
|
|
++first_table_version) {
|
|
|
|
BlockBasedTableOptions table_options;
|
|
|
|
table_options.format_version = first_table_version;
|
|
|
|
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.compression = compressions[iter];
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
int kNumKeysWritten = 100000;
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < kNumKeysWritten; ++i) {
|
|
|
|
// compressible string
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 128) + std::string(128, 'a')));
|
|
|
|
}
|
|
|
|
|
|
|
|
table_options.format_version = first_table_version == 1 ? 2 : 1;
|
|
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
|
|
Reopen(options);
|
|
|
|
for (int i = 0; i < kNumKeysWritten; ++i) {
|
|
|
|
auto r = Get(Key(i));
|
|
|
|
ASSERT_EQ(r.substr(128), std::string(128, 'a'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, MutexWaitStats) {
|
2015-02-05 06:39:45 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
const int64_t kMutexWaitDelay = 100;
|
|
|
|
ThreadStatusUtil::TEST_SetStateDelay(
|
|
|
|
ThreadStatus::STATE_MUTEX_WAIT, kMutexWaitDelay);
|
|
|
|
ASSERT_OK(Put("hello", "rocksdb"));
|
|
|
|
ASSERT_GE(TestGetTickerCount(
|
|
|
|
options, DB_MUTEX_WAIT_MICROS), kMutexWaitDelay);
|
|
|
|
ThreadStatusUtil::TEST_SetStateDelay(
|
|
|
|
ThreadStatus::STATE_MUTEX_WAIT, 0);
|
|
|
|
}
|
|
|
|
|
2015-02-10 02:38:32 +01:00
|
|
|
// This reproduces a bug where we don't delete a file because when it was
|
|
|
|
// supposed to be deleted, it was blocked by pending_outputs
|
|
|
|
// Consider:
|
|
|
|
// 1. current file_number is 13
|
|
|
|
// 2. compaction (1) starts, blocks deletion of all files starting with 13
|
|
|
|
// (pending outputs)
|
|
|
|
// 3. file 13 is created by compaction (2)
|
|
|
|
// 4. file 13 is consumed by compaction (3) and file 15 was created. Since file
|
|
|
|
// 13 has no references, it is put into VersionSet::obsolete_files_
|
|
|
|
// 5. FindObsoleteFiles() gets file 13 from VersionSet::obsolete_files_. File 13
|
|
|
|
// is deleted from obsolete_files_ set.
|
|
|
|
// 6. PurgeObsoleteFiles() tries to delete file 13, but this file is blocked by
|
|
|
|
// pending outputs since compaction (1) is still running. It is not deleted and
|
|
|
|
// it is not present in obsolete_files_ anymore. Therefore, we never delete it.
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, DeleteObsoleteFilesPendingOutputs) {
|
2015-02-10 02:38:32 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.env = env_;
|
|
|
|
options.write_buffer_size = 2 * 1024 * 1024; // 2 MB
|
|
|
|
options.max_bytes_for_level_base = 1024 * 1024; // 1 MB
|
|
|
|
options.level0_file_num_compaction_trigger =
|
|
|
|
2; // trigger compaction when we have 2 files
|
|
|
|
options.max_background_flushes = 2;
|
|
|
|
options.max_background_compactions = 2;
|
2015-06-04 04:57:01 +02:00
|
|
|
|
|
|
|
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
|
|
|
options.listeners.emplace_back(listener);
|
|
|
|
|
2015-02-10 02:38:32 +01:00
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
// Create two 1MB sst files
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
// Create 1MB sst file
|
|
|
|
for (int j = 0; j < 100; ++j) {
|
|
|
|
ASSERT_OK(Put(Key(i * 50 + j), RandomString(&rnd, 10 * 1024)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
// this should execute both L0->L1 and L1->(move)->L2 compactions
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
ASSERT_EQ("0,0,1", FilesPerLevel(0));
|
|
|
|
|
|
|
|
SleepingBackgroundTask blocking_thread;
|
|
|
|
port::Mutex mutex_;
|
|
|
|
bool already_blocked(false);
|
|
|
|
|
|
|
|
// block the flush
|
|
|
|
std::function<void()> block_first_time = [&]() {
|
|
|
|
bool blocking = false;
|
|
|
|
{
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
if (!already_blocked) {
|
|
|
|
blocking = true;
|
|
|
|
already_blocked = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (blocking) {
|
|
|
|
blocking_thread.DoSleep();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
env_->table_write_callback_ = &block_first_time;
|
|
|
|
// Create 1MB sst file
|
|
|
|
for (int j = 0; j < 256; ++j) {
|
|
|
|
ASSERT_OK(Put(Key(j), RandomString(&rnd, 10 * 1024)));
|
|
|
|
}
|
|
|
|
// this should trigger a flush, which is blocked with block_first_time
|
|
|
|
// pending_file is protecting all the files created after
|
|
|
|
|
|
|
|
ASSERT_OK(dbfull()->TEST_CompactRange(2, nullptr, nullptr));
|
|
|
|
|
|
|
|
ASSERT_EQ("0,0,0,1", FilesPerLevel(0));
|
|
|
|
std::vector<LiveFileMetaData> metadata;
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
|
|
|
ASSERT_EQ(metadata.size(), 1U);
|
|
|
|
auto file_on_L2 = metadata[0].name;
|
2015-06-04 04:57:01 +02:00
|
|
|
listener->SetExpectedFileName(dbname_ + file_on_L2);
|
2015-02-10 02:38:32 +01:00
|
|
|
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
ASSERT_OK(dbfull()->TEST_CompactRange(3, nullptr, nullptr, nullptr,
|
|
|
|
true /* disallow trivial move */));
|
2015-02-10 02:38:32 +01:00
|
|
|
ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// finish the flush!
|
|
|
|
blocking_thread.WakeUp();
|
|
|
|
blocking_thread.WaitUntilDone();
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
ASSERT_EQ("1,0,0,0,1", FilesPerLevel(0));
|
|
|
|
|
|
|
|
metadata.clear();
|
|
|
|
db_->GetLiveFilesMetaData(&metadata);
|
|
|
|
ASSERT_EQ(metadata.size(), 2U);
|
|
|
|
|
Allowing L0 -> L1 trivial move on sorted data
Summary:
This diff updates the logic of how we do trivial move, now trivial move can run on any number of files in input level as long as they are not overlapping
The conditions for trivial move have been updated
Introduced conditions:
- Trivial move cannot happen if we have a compaction filter (except if the compaction is not manual)
- Input level files cannot be overlapping
Removed conditions:
- Trivial move only run when the compaction is not manual
- Input level should can contain only 1 file
More context on what tests failed because of Trivial move
```
DBTest.CompactionsGenerateMultipleFiles
This test is expecting compaction on a file in L0 to generate multiple files in L1, this test will fail with trivial move because we end up with one file in L1
```
```
DBTest.NoSpaceCompactRange
This test expect compaction to fail when we force environment to report running out of space, of course this is not valid in trivial move situation
because trivial move does not need any extra space, and did not check for that
```
```
DBTest.DropWrites
Similar to DBTest.NoSpaceCompactRange
```
```
DBTest.DeleteObsoleteFilesPendingOutputs
This test expect that a file in L2 is deleted after it's moved to L3, this is not valid with trivial move because although the file was moved it is now used by L3
```
```
CuckooTableDBTest.CompactionIntoMultipleFiles
Same as DBTest.CompactionsGenerateMultipleFiles
```
This diff is based on a work by @sdong https://reviews.facebook.net/D34149
Test Plan: make -j64 check
Reviewers: rven, sdong, igor
Reviewed By: igor
Subscribers: yhchiang, ott, march, dhruba, sdong
Differential Revision: https://reviews.facebook.net/D34797
2015-06-05 01:51:25 +02:00
|
|
|
// This file should have been deleted during last compaction
|
2015-06-04 04:57:01 +02:00
|
|
|
ASSERT_TRUE(!env_->FileExists(dbname_ + file_on_L2));
|
|
|
|
listener->VerifyMatchedCount(1);
|
2015-02-10 02:38:32 +01:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:08:00 +01:00
|
|
|
TEST_F(DBTest, CloseSpeedup) {
|
2015-03-17 02:49:14 +01:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.max_bytes_for_level_base = 400 * 1024;
|
|
|
|
options.max_write_buffer_number = 16;
|
|
|
|
|
|
|
|
// Block background threads
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
|
|
|
SleepingBackgroundTask sleeping_task_low;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
|
|
|
Env::Priority::LOW);
|
|
|
|
SleepingBackgroundTask sleeping_task_high;
|
|
|
|
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_high,
|
|
|
|
Env::Priority::HIGH);
|
|
|
|
|
|
|
|
std::vector<std::string> filenames;
|
|
|
|
env_->GetChildren(dbname_, &filenames);
|
|
|
|
// Delete archival files.
|
|
|
|
for (size_t i = 0; i < filenames.size(); ++i) {
|
|
|
|
env_->DeleteFile(dbname_ + "/" + filenames[i]);
|
|
|
|
}
|
|
|
|
env_->DeleteDir(dbname_);
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
env_->SetBackgroundThreads(1, Env::LOW);
|
|
|
|
env_->SetBackgroundThreads(1, Env::HIGH);
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// First three 110KB files are not going to level 2
|
|
|
|
// After that, (100K, 200K)
|
|
|
|
for (int num = 0; num < 5; num++) {
|
|
|
|
GenerateNewFile(&rnd, &key_idx, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
Close();
|
|
|
|
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// Unblock background threads
|
|
|
|
sleeping_task_high.WakeUp();
|
|
|
|
sleeping_task_high.WaitUntilDone();
|
|
|
|
sleeping_task_low.WakeUp();
|
|
|
|
sleeping_task_low.WaitUntilDone();
|
|
|
|
|
|
|
|
Destroy(options);
|
|
|
|
}
|
2015-03-03 19:59:36 +01:00
|
|
|
|
|
|
|
class DelayedMergeOperator : public AssociativeMergeOperator {
|
|
|
|
private:
|
|
|
|
DBTest* db_test_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit DelayedMergeOperator(DBTest* d) : db_test_(d) {}
|
|
|
|
virtual bool Merge(const Slice& key, const Slice* existing_value,
|
|
|
|
const Slice& value, std::string* new_value,
|
|
|
|
Logger* logger) const override {
|
2015-05-16 00:52:51 +02:00
|
|
|
db_test_->env_->addon_time_.fetch_add(1000);
|
2015-03-03 19:59:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual const char* Name() const override { return "DelayedMergeOperator"; }
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(DBTest, MergeTestTime) {
|
|
|
|
std::string one, two, three;
|
|
|
|
PutFixed64(&one, 1);
|
|
|
|
PutFixed64(&two, 2);
|
|
|
|
PutFixed64(&three, 3);
|
|
|
|
|
|
|
|
// Enable time profiling
|
|
|
|
SetPerfLevel(kEnableTime);
|
2015-05-16 00:52:51 +02:00
|
|
|
this->env_->addon_time_.store(0);
|
2015-03-03 19:59:36 +01:00
|
|
|
Options options;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
options.merge_operator.reset(new DelayedMergeOperator(this));
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
ASSERT_EQ(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 0);
|
|
|
|
db_->Put(WriteOptions(), "foo", one);
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), "foo", two));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), "foo", three));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
|
|
|
|
ReadOptions opt;
|
|
|
|
opt.verify_checksums = true;
|
|
|
|
opt.snapshot = nullptr;
|
|
|
|
std::string result;
|
|
|
|
db_->Get(opt, "foo", &result);
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_LT(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 2800000);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 1200000);
|
2015-03-03 19:59:36 +01:00
|
|
|
|
|
|
|
ReadOptions read_options;
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
|
|
int count = 0;
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
ASSERT_OK(iter->status());
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(1, count);
|
|
|
|
|
2015-03-30 23:04:21 +02:00
|
|
|
ASSERT_LT(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 6000000);
|
|
|
|
ASSERT_GT(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 3200000);
|
2015-03-03 19:59:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DBTest, MergeCompactionTimeTest) {
|
|
|
|
SetPerfLevel(kEnableTime);
|
|
|
|
Options options;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.compaction_filter_factory = std::make_shared<KeepFilterFactory>();
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
options.merge_operator.reset(new DelayedMergeOperator(this));
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
|
|
ASSERT_OK(db_->Merge(WriteOptions(), "foo", "TEST"));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
ASSERT_NE(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DBTest, FilterCompactionTimeTest) {
|
|
|
|
Options options;
|
|
|
|
options.compaction_filter_factory =
|
|
|
|
std::make_shared<DelayFilterFactory>(this);
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.statistics = rocksdb::CreateDBStatistics();
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// put some data
|
|
|
|
for (int table = 0; table < 4; ++table) {
|
|
|
|
for (int i = 0; i < 10 + table; ++i) {
|
|
|
|
Put(ToString(table * 100 + i), "val");
|
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
2015-03-03 19:59:36 +01:00
|
|
|
ASSERT_EQ(0U, CountLiveFiles());
|
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
Iterator* itr = db_->NewIterator(ReadOptions());
|
|
|
|
itr->SeekToFirst();
|
|
|
|
ASSERT_NE(TestGetTickerCount(options, FILTER_OPERATION_TOTAL_TIME), 0);
|
|
|
|
delete itr;
|
|
|
|
}
|
|
|
|
|
2015-03-30 21:04:10 +02:00
|
|
|
TEST_F(DBTest, TestLogCleanup) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.write_buffer_size = 64 * 1024; // very small
|
|
|
|
// only two memtables allowed ==> only two log files
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
for (int i = 0; i < 100000; ++i) {
|
|
|
|
Put(Key(i), "val");
|
|
|
|
// only 2 memtables will be alive, so logs_to_free needs to always be below
|
|
|
|
// 2
|
|
|
|
ASSERT_LT(dbfull()->TEST_LogsToFreeSize(), static_cast<size_t>(3));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-02 01:55:08 +02:00
|
|
|
TEST_F(DBTest, EmptyCompactedDB) {
|
|
|
|
Options options;
|
|
|
|
options.max_open_files = -1;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
Close();
|
|
|
|
ASSERT_OK(ReadOnlyReopen(options));
|
|
|
|
Status s = Put("new", "value");
|
|
|
|
ASSERT_TRUE(s.IsNotSupported());
|
|
|
|
Close();
|
|
|
|
}
|
|
|
|
|
2015-04-02 20:06:30 +02:00
|
|
|
TEST_F(DBTest, CompressLevelCompaction) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.max_bytes_for_level_base = 400 * 1024;
|
|
|
|
// First two levels have no compression, so that a trivial move between
|
|
|
|
// them will be allowed. Level 2 has Zlib compression so that a trivial
|
|
|
|
// move to level 3 will not be allowed
|
|
|
|
options.compression_per_level = {kNoCompression, kNoCompression,
|
|
|
|
kZlibCompression};
|
|
|
|
int matches = 0, didnt_match = 0, trivial_move = 0, non_trivial = 0;
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"Compaction::InputCompressionMatchesOutput:Matches",
|
2015-04-14 10:55:19 +02:00
|
|
|
[&](void* arg) { matches++; });
|
2015-04-02 20:06:30 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"Compaction::InputCompressionMatchesOutput:DidntMatch",
|
2015-04-14 10:55:19 +02:00
|
|
|
[&](void* arg) { didnt_match++; });
|
2015-04-02 20:06:30 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
2015-04-14 10:55:19 +02:00
|
|
|
"DBImpl::BackgroundCompaction:NonTrivial",
|
|
|
|
[&](void* arg) { non_trivial++; });
|
2015-04-02 20:06:30 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
2015-04-14 10:55:19 +02:00
|
|
|
"DBImpl::BackgroundCompaction:TrivialMove",
|
|
|
|
[&](void* arg) { trivial_move++; });
|
2015-04-02 20:06:30 +02:00
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
int key_idx = 0;
|
|
|
|
|
|
|
|
// First three 110KB files are going to level 0
|
|
|
|
// After that, (100K, 200K)
|
|
|
|
for (int num = 0; num < 3; num++) {
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Another 110KB triggers a compaction to 400K file to fill up level 0
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ(4, GetSstFileCount(dbname_));
|
|
|
|
|
|
|
|
// (1, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 1)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,1", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 2)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,2", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 3)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,3", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 4)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,4", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 5)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,5", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 6)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,6", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 7)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,7", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// (1, 4, 8)
|
|
|
|
GenerateNewFile(&rnd, &key_idx);
|
|
|
|
ASSERT_EQ("1,4,8", FilesPerLevel(0));
|
|
|
|
|
|
|
|
ASSERT_EQ(matches, 12);
|
2015-05-07 07:50:35 +02:00
|
|
|
// Currently, the test relies on the number of calls to
|
|
|
|
// InputCompressionMatchesOutput() per compaction.
|
|
|
|
const int kCallsToInputCompressionMatch = 2;
|
|
|
|
ASSERT_EQ(didnt_match, 8 * kCallsToInputCompressionMatch);
|
2015-04-02 20:06:30 +02:00
|
|
|
ASSERT_EQ(trivial_move, 12);
|
|
|
|
ASSERT_EQ(non_trivial, 8);
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
for (int i = 0; i < key_idx; i++) {
|
|
|
|
auto v = Get(Key(i));
|
|
|
|
ASSERT_NE(v, "NOT_FOUND");
|
|
|
|
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
Destroy(options);
|
|
|
|
}
|
|
|
|
|
2015-06-04 21:03:40 +02:00
|
|
|
class CountingDeleteTabPropCollector : public TablePropertiesCollector {
|
|
|
|
public:
|
|
|
|
const char* Name() const override { return "CountingDeleteTabPropCollector"; }
|
|
|
|
|
|
|
|
Status AddUserKey(const Slice& user_key, const Slice& value, EntryType type,
|
|
|
|
SequenceNumber seq, uint64_t file_size) override {
|
|
|
|
if (type == kEntryDelete) {
|
|
|
|
num_deletes_++;
|
|
|
|
}
|
|
|
|
return Status::OK();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NeedCompact() const override { return num_deletes_ > 10; }
|
|
|
|
|
|
|
|
UserCollectedProperties GetReadableProperties() const override {
|
|
|
|
return UserCollectedProperties{};
|
|
|
|
}
|
|
|
|
|
|
|
|
Status Finish(UserCollectedProperties* properties) override {
|
|
|
|
*properties =
|
|
|
|
UserCollectedProperties{{"num_delete", ToString(num_deletes_)}};
|
|
|
|
return Status::OK();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
uint32_t num_deletes_ = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
class CountingDeleteTabPropCollectorFactory
|
|
|
|
: public TablePropertiesCollectorFactory {
|
|
|
|
public:
|
|
|
|
virtual TablePropertiesCollector* CreateTablePropertiesCollector() override {
|
|
|
|
return new CountingDeleteTabPropCollector();
|
|
|
|
}
|
|
|
|
const char* Name() const override {
|
|
|
|
return "CountingDeleteTabPropCollectorFactory";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(DBTest, TablePropertiesNeedCompactTest) {
|
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
Options options;
|
|
|
|
options.create_if_missing = true;
|
|
|
|
options.write_buffer_size = 4096;
|
|
|
|
options.max_write_buffer_number = 8;
|
|
|
|
options.level0_file_num_compaction_trigger = 2;
|
|
|
|
options.level0_slowdown_writes_trigger = 2;
|
|
|
|
options.level0_stop_writes_trigger = 4;
|
|
|
|
options.target_file_size_base = 2048;
|
|
|
|
options.max_bytes_for_level_base = 10240;
|
|
|
|
options.max_bytes_for_level_multiplier = 4;
|
2015-05-16 00:52:51 +02:00
|
|
|
options.soft_rate_limit = 1.1;
|
2015-06-04 21:03:40 +02:00
|
|
|
options.num_levels = 8;
|
|
|
|
|
|
|
|
std::shared_ptr<TablePropertiesCollectorFactory> collector_factory(
|
|
|
|
new CountingDeleteTabPropCollectorFactory);
|
|
|
|
options.table_properties_collector_factories.resize(1);
|
|
|
|
options.table_properties_collector_factories[0] = collector_factory;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
const int kMaxKey = 1000;
|
|
|
|
for (int i = 0; i < kMaxKey; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 102)));
|
|
|
|
ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
|
|
|
|
}
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
if (NumTableFilesAtLevel(0) == 1) {
|
|
|
|
// Clear Level 0 so that when later flush a file with deletions,
|
|
|
|
// we don't trigger an organic compaction.
|
|
|
|
ASSERT_OK(Put(Key(0), ""));
|
|
|
|
ASSERT_OK(Put(Key(kMaxKey * 2), ""));
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
|
|
|
|
|
|
|
{
|
|
|
|
int c = 0;
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
|
|
|
iter->Seek(Key(kMaxKey - 100));
|
|
|
|
while (iter->Valid() && iter->key().compare(Key(kMaxKey + 100)) < 0) {
|
|
|
|
iter->Next();
|
|
|
|
++c;
|
|
|
|
}
|
|
|
|
ASSERT_EQ(c, 200);
|
|
|
|
}
|
|
|
|
|
|
|
|
Delete(Key(0));
|
|
|
|
for (int i = kMaxKey - 100; i < kMaxKey + 100; i++) {
|
|
|
|
Delete(Key(i));
|
|
|
|
}
|
|
|
|
Delete(Key(kMaxKey * 2));
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
{
|
|
|
|
SetPerfLevel(kEnableCount);
|
|
|
|
perf_context.Reset();
|
|
|
|
int c = 0;
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
|
|
|
iter->Seek(Key(kMaxKey - 100));
|
|
|
|
while (iter->Valid() && iter->key().compare(Key(kMaxKey + 100)) < 0) {
|
|
|
|
iter->Next();
|
|
|
|
}
|
|
|
|
ASSERT_EQ(c, 0);
|
|
|
|
ASSERT_LT(perf_context.internal_delete_skipped_count, 30u);
|
|
|
|
ASSERT_LT(perf_context.internal_key_skipped_count, 30u);
|
|
|
|
SetPerfLevel(kDisable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
TEST_F(DBTest, SuggestCompactRangeTest) {
|
2015-05-09 04:37:02 +02:00
|
|
|
class CompactionFilterFactoryGetContext : public CompactionFilterFactory {
|
|
|
|
public:
|
|
|
|
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
2015-05-09 20:03:36 +02:00
|
|
|
const CompactionFilter::Context& context) override {
|
2015-05-09 04:37:02 +02:00
|
|
|
saved_context = context;
|
|
|
|
std::unique_ptr<CompactionFilter> empty_filter;
|
|
|
|
return empty_filter;
|
|
|
|
}
|
|
|
|
const char* Name() const override {
|
|
|
|
return "CompactionFilterFactoryGetContext";
|
|
|
|
}
|
|
|
|
static bool IsManual(CompactionFilterFactory* compaction_filter_factory) {
|
|
|
|
return reinterpret_cast<CompactionFilterFactoryGetContext*>(
|
2015-06-04 21:03:40 +02:00
|
|
|
compaction_filter_factory)->saved_context.is_manual_compaction;
|
2015-05-09 04:37:02 +02:00
|
|
|
}
|
|
|
|
CompactionFilter::Context saved_context;
|
|
|
|
};
|
|
|
|
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
2015-05-09 04:37:02 +02:00
|
|
|
options.compaction_filter_factory.reset(
|
|
|
|
new CompactionFilterFactoryGetContext());
|
|
|
|
options.write_buffer_size = 110 << 10;
|
|
|
|
options.level0_file_num_compaction_trigger = 4;
|
2015-04-29 22:35:48 +02:00
|
|
|
options.num_levels = 4;
|
2015-05-09 04:37:02 +02:00
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.max_bytes_for_level_base = 450 << 10;
|
|
|
|
options.target_file_size_base = 98 << 10;
|
|
|
|
options.max_grandparent_overlap_factor = 1 << 20; // inf
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
|
|
|
|
for (int num = 0; num < 3; num++) {
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
}
|
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
ASSERT_EQ("0,4", FilesPerLevel(0));
|
|
|
|
ASSERT_TRUE(!CompactionFilterFactoryGetContext::IsManual(
|
|
|
|
options.compaction_filter_factory.get()));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
ASSERT_EQ("1,4", FilesPerLevel(0));
|
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
ASSERT_EQ("2,4", FilesPerLevel(0));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
ASSERT_EQ("3,4", FilesPerLevel(0));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
ASSERT_EQ("0,4,4", FilesPerLevel(0));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
ASSERT_EQ("1,4,4", FilesPerLevel(0));
|
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
ASSERT_EQ("2,4,4", FilesPerLevel(0));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
ASSERT_EQ("3,4,4", FilesPerLevel(0));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
ASSERT_EQ("0,4,8", FilesPerLevel(0));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
ASSERT_EQ("1,4,8", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// compact it three times
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
|
|
ASSERT_OK(experimental::SuggestCompactRange(db_, nullptr, nullptr));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ("0,0,13", FilesPerLevel(0));
|
|
|
|
|
2015-05-09 04:37:02 +02:00
|
|
|
GenerateNewRandomFile(&rnd);
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
ASSERT_EQ("1,0,13", FilesPerLevel(0));
|
|
|
|
|
|
|
|
// nonoverlapping with the file on level 0
|
|
|
|
Slice start("a"), end("b");
|
|
|
|
ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
|
|
|
|
|
|
|
// should not compact the level 0 file
|
|
|
|
ASSERT_EQ("1,0,13", FilesPerLevel(0));
|
|
|
|
|
|
|
|
start = Slice("j");
|
|
|
|
end = Slice("m");
|
|
|
|
ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end));
|
|
|
|
dbfull()->TEST_WaitForCompact();
|
2015-05-09 04:37:02 +02:00
|
|
|
ASSERT_TRUE(CompactionFilterFactoryGetContext::IsManual(
|
|
|
|
options.compaction_filter_factory.get()));
|
Add experimental API MarkForCompaction()
Summary:
Some Mongo+Rocks datasets in Parse's environment are not doing compactions very frequently. During the quiet period (with no IO), we'd like to schedule compactions so that our reads become faster. Also, aggressively compacting during quiet periods helps when write bursts happen. In addition, we also want to compact files that are containing deleted key ranges (like old oplog keys).
All of this is currently not possible with CompactRange() because it's single-threaded and blocks all other compactions from happening. Running CompactRange() risks an issue of blocking writes because we generate too much Level 0 files before the compaction is over. Stopping writes is very dangerous because they hold transaction locks. We tried running manual compaction once on Mongo+Rocks and everything fell apart.
MarkForCompaction() solves all of those problems. This is very light-weight manual compaction. It is lower priority than automatic compactions, which means it shouldn't interfere with background process keeping the LSM tree clean. However, if no automatic compactions need to be run (or we have extra background threads available), we will start compacting files that are marked for compaction.
Test Plan: added a new unit test
Reviewers: yhchiang, rven, MarkCallaghan, sdong
Reviewed By: sdong
Subscribers: yoshinorim, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D37083
2015-04-18 01:44:45 +02:00
|
|
|
|
|
|
|
// now it should compact the level 0 file
|
|
|
|
ASSERT_EQ("0,1,13", FilesPerLevel(0));
|
|
|
|
}
|
|
|
|
|
Implement DB::PromoteL0 method
Summary:
This diff implements a new `DB` method `PromoteL0` which moves all files in L0
to a given level skipping compaction, provided that the files have disjoint
ranges and all levels up to the target level are empty.
This method provides finer-grain control for trivial compactions, and it is
useful for bulk-loading pre-sorted keys. Compared to D34797, it does not change
the semantics of an existing operation, which can impact existing code.
PromoteL0 is designed to work well in combination with the proposed
`GetSstFileWriter`/`AddFile` interface, enabling to "design" the level structure
by populating one level at a time. Such fine-grained control can be very useful
for static or mostly-static databases.
Test Plan: `make check`
Reviewers: IslamAbdelRahman, philipp, MarkCallaghan, yhchiang, igor, sdong
Reviewed By: sdong
Subscribers: dhruba
Differential Revision: https://reviews.facebook.net/D37107
2015-04-23 21:10:36 +02:00
|
|
|
TEST_F(DBTest, PromoteL0) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.write_buffer_size = 10 * 1024 * 1024;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// non overlapping ranges
|
|
|
|
std::vector<std::pair<int32_t, int32_t>> ranges = {
|
|
|
|
{81, 160}, {0, 80}, {161, 240}, {241, 320}};
|
|
|
|
|
|
|
|
int32_t value_size = 10 * 1024; // 10 KB
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
std::map<int32_t, std::string> values;
|
|
|
|
for (const auto& range : ranges) {
|
|
|
|
for (int32_t j = range.first; j < range.second; j++) {
|
|
|
|
values[j] = RandomString(&rnd, value_size);
|
|
|
|
ASSERT_OK(Put(Key(j), values[j]));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t level0_files = NumTableFilesAtLevel(0, 0);
|
|
|
|
ASSERT_EQ(level0_files, ranges.size());
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // No files in L1
|
|
|
|
|
|
|
|
// Promote L0 level to L2.
|
|
|
|
ASSERT_OK(experimental::PromoteL0(db_, db_->DefaultColumnFamily(), 2));
|
|
|
|
// We expect that all the files were trivially moved from L0 to L2
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0);
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(2, 0), level0_files);
|
|
|
|
|
|
|
|
for (const auto& kv : values) {
|
|
|
|
ASSERT_EQ(Get(Key(kv.first)), kv.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DBTest, PromoteL0Failure) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.write_buffer_size = 10 * 1024 * 1024;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// Produce two L0 files with overlapping ranges.
|
|
|
|
ASSERT_OK(Put(Key(0), ""));
|
|
|
|
ASSERT_OK(Put(Key(3), ""));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
ASSERT_OK(Put(Key(1), ""));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
|
|
|
|
Status status;
|
|
|
|
// Fails because L0 has overlapping files.
|
|
|
|
status = experimental::PromoteL0(db_, db_->DefaultColumnFamily());
|
|
|
|
ASSERT_TRUE(status.IsInvalidArgument());
|
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
Implement DB::PromoteL0 method
Summary:
This diff implements a new `DB` method `PromoteL0` which moves all files in L0
to a given level skipping compaction, provided that the files have disjoint
ranges and all levels up to the target level are empty.
This method provides finer-grain control for trivial compactions, and it is
useful for bulk-loading pre-sorted keys. Compared to D34797, it does not change
the semantics of an existing operation, which can impact existing code.
PromoteL0 is designed to work well in combination with the proposed
`GetSstFileWriter`/`AddFile` interface, enabling to "design" the level structure
by populating one level at a time. Such fine-grained control can be very useful
for static or mostly-static databases.
Test Plan: `make check`
Reviewers: IslamAbdelRahman, philipp, MarkCallaghan, yhchiang, igor, sdong
Reviewed By: sdong
Subscribers: dhruba
Differential Revision: https://reviews.facebook.net/D37107
2015-04-23 21:10:36 +02:00
|
|
|
// Now there is a file in L1.
|
|
|
|
ASSERT_GE(NumTableFilesAtLevel(1, 0), 1);
|
|
|
|
|
|
|
|
ASSERT_OK(Put(Key(5), ""));
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
// Fails because L1 is non-empty.
|
|
|
|
status = experimental::PromoteL0(db_, db_->DefaultColumnFamily());
|
|
|
|
ASSERT_TRUE(status.IsInvalidArgument());
|
|
|
|
}
|
|
|
|
|
2015-04-29 19:52:31 +02:00
|
|
|
// Github issue #596
|
|
|
|
TEST_F(DBTest, HugeNumberOfLevels) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.write_buffer_size = 2 * 1024 * 1024; // 2MB
|
|
|
|
options.max_bytes_for_level_base = 2 * 1024 * 1024; // 2MB
|
|
|
|
options.num_levels = 12;
|
|
|
|
options.max_background_compactions = 10;
|
|
|
|
options.max_bytes_for_level_multiplier = 2;
|
|
|
|
options.level_compaction_dynamic_level_bytes = true;
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < 300000; ++i) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
|
|
|
}
|
|
|
|
|
2015-06-17 23:36:14 +02:00
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
2015-04-29 19:52:31 +02:00
|
|
|
}
|
|
|
|
|
2015-05-02 00:41:50 +02:00
|
|
|
// Github issue #595
|
|
|
|
// Large write batch with column families
|
|
|
|
TEST_F(DBTest, LargeBatchWithColumnFamilies) {
|
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
int64_t j = 0;
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
|
|
for (int pass = 1; pass <= 3; pass++) {
|
|
|
|
WriteBatch batch;
|
|
|
|
size_t write_size = 1024 * 1024 * (5 + i);
|
|
|
|
fprintf(stderr, "prepare: %ld MB, pass:%d\n", (write_size / 1024 / 1024),
|
|
|
|
pass);
|
|
|
|
for (;;) {
|
|
|
|
std::string data(3000, j++ % 127 + 20);
|
2015-06-08 20:43:55 +02:00
|
|
|
data += ToString(j);
|
2015-05-02 00:41:50 +02:00
|
|
|
batch.Put(handles_[0], Slice(data), Slice(data));
|
|
|
|
if (batch.GetDataSize() > write_size) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fprintf(stderr, "write: %ld MB\n", (batch.GetDataSize() / 1024 / 1024));
|
|
|
|
ASSERT_OK(dbfull()->Write(WriteOptions(), &batch));
|
|
|
|
fprintf(stderr, "done\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// make sure we can re-open it.
|
|
|
|
ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options));
|
|
|
|
}
|
|
|
|
|
2015-05-19 00:34:33 +02:00
|
|
|
// Make sure that Flushes can proceed in parallel with CompactRange()
|
|
|
|
TEST_F(DBTest, FlushesInParallelWithCompactRange) {
|
|
|
|
// iter == 0 -- leveled
|
|
|
|
// iter == 1 -- leveled, but throw in a flush between two levels compacting
|
|
|
|
// iter == 2 -- universal
|
|
|
|
for (int iter = 0; iter < 3; ++iter) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
if (iter < 2) {
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
} else {
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
}
|
|
|
|
options.write_buffer_size = 110 << 10;
|
|
|
|
options.level0_file_num_compaction_trigger = 4;
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.max_bytes_for_level_base = 450 << 10;
|
|
|
|
options.target_file_size_base = 98 << 10;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
Random rnd(301);
|
|
|
|
for (int num = 0; num < 14; num++) {
|
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iter == 1) {
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
|
|
|
{{"DBImpl::RunManualCompaction()::1",
|
|
|
|
"DBTest::FlushesInParallelWithCompactRange:1"},
|
|
|
|
{"DBTest::FlushesInParallelWithCompactRange:2",
|
|
|
|
"DBImpl::RunManualCompaction()::2"}});
|
|
|
|
} else {
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
|
|
|
{{"CompactionJob::Run():Start",
|
|
|
|
"DBTest::FlushesInParallelWithCompactRange:1"},
|
|
|
|
{"DBTest::FlushesInParallelWithCompactRange:2",
|
|
|
|
"CompactionJob::Run():End"}});
|
|
|
|
}
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
std::vector<std::thread> threads;
|
|
|
|
threads.emplace_back([&]() { Compact("a", "z"); });
|
|
|
|
|
|
|
|
TEST_SYNC_POINT("DBTest::FlushesInParallelWithCompactRange:1");
|
|
|
|
|
|
|
|
// this has to start a flush. if flushes are blocked, this will try to
|
|
|
|
// create
|
|
|
|
// 3 memtables, and that will fail because max_write_buffer_number is 2
|
|
|
|
for (int num = 0; num < 3; num++) {
|
|
|
|
GenerateNewRandomFile(&rnd, /* nowait */ true);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_SYNC_POINT("DBTest::FlushesInParallelWithCompactRange:2");
|
|
|
|
|
|
|
|
for (auto& t : threads) {
|
|
|
|
t.join();
|
|
|
|
}
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-11 23:15:52 +02:00
|
|
|
TEST_F(DBTest, UniversalCompactionTargetLevel) {
|
|
|
|
Options options;
|
|
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
|
|
options.write_buffer_size = 100 << 10; // 100KB
|
|
|
|
options.num_levels = 7;
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// Generate 3 overlapping files
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < 210; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 100)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
|
|
|
|
for (int i = 200; i < 300; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 100)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
|
|
|
|
for (int i = 250; i < 260; i++) {
|
|
|
|
ASSERT_OK(Put(Key(i), RandomString(&rnd, 100)));
|
|
|
|
}
|
|
|
|
ASSERT_OK(Flush());
|
|
|
|
|
|
|
|
ASSERT_EQ("3", FilesPerLevel(0));
|
|
|
|
// Compact all files into 1 file and put it in L4
|
2015-06-17 23:36:14 +02:00
|
|
|
CompactRangeOptions compact_options;
|
|
|
|
compact_options.change_level = true;
|
|
|
|
compact_options.target_level = 4;
|
|
|
|
db_->CompactRange(compact_options, nullptr, nullptr);
|
2015-06-11 23:15:52 +02:00
|
|
|
ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0));
|
|
|
|
}
|
|
|
|
|
2015-06-12 00:42:16 +02:00
|
|
|
// This tests for a bug that could cause two level0 compactions running
|
|
|
|
// concurrently
|
|
|
|
TEST_F(DBTest, SuggestCompactRangeNoTwoLevel0Compactions) {
|
|
|
|
Options options = CurrentOptions();
|
|
|
|
options.compaction_style = kCompactionStyleLevel;
|
|
|
|
options.write_buffer_size = 110 << 10;
|
|
|
|
options.level0_file_num_compaction_trigger = 4;
|
|
|
|
options.num_levels = 4;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
options.max_bytes_for_level_base = 450 << 10;
|
|
|
|
options.target_file_size_base = 98 << 10;
|
|
|
|
options.max_write_buffer_number = 2;
|
|
|
|
options.max_background_compactions = 2;
|
|
|
|
|
|
|
|
DestroyAndReopen(options);
|
|
|
|
|
|
|
|
// fill up the DB
|
|
|
|
Random rnd(301);
|
|
|
|
for (int num = 0; num < 10; num++) {
|
|
|
|
GenerateNewRandomFile(&rnd);
|
|
|
|
}
|
2015-06-17 23:36:14 +02:00
|
|
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
2015-06-12 00:42:16 +02:00
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
|
|
|
{{"CompactionJob::Run():Start",
|
|
|
|
"DBTest::SuggestCompactRangeNoTwoLevel0Compactions:1"},
|
|
|
|
{"DBTest::SuggestCompactRangeNoTwoLevel0Compactions:2",
|
|
|
|
"CompactionJob::Run():End"}});
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
// trigger L0 compaction
|
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger + 1;
|
|
|
|
num++) {
|
|
|
|
GenerateNewRandomFile(&rnd, /* nowait */ true);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_SYNC_POINT("DBTest::SuggestCompactRangeNoTwoLevel0Compactions:1");
|
|
|
|
|
|
|
|
GenerateNewRandomFile(&rnd, /* nowait */ true);
|
|
|
|
dbfull()->TEST_WaitForFlushMemTable();
|
|
|
|
ASSERT_OK(experimental::SuggestCompactRange(db_, nullptr, nullptr));
|
|
|
|
for (int num = 0; num < options.level0_file_num_compaction_trigger + 1;
|
|
|
|
num++) {
|
|
|
|
GenerateNewRandomFile(&rnd, /* nowait */ true);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_SYNC_POINT("DBTest::SuggestCompactRangeNoTwoLevel0Compactions:2");
|
|
|
|
}
|
|
|
|
|
2015-05-16 00:52:51 +02:00
|
|
|
TEST_F(DBTest, DelayedWriteRate) {
|
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
env_->no_sleep_ = true;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
|
|
|
options.max_write_buffer_number = 256;
|
|
|
|
options.disable_auto_compactions = true;
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
|
|
|
options.level0_slowdown_writes_trigger = 3;
|
|
|
|
options.level0_stop_writes_trigger = 999999;
|
|
|
|
options.delayed_write_rate = 200000; // About 200KB/s limited rate
|
|
|
|
|
|
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
Put(Key(i), std::string(10000, 'x'));
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
// These writes will be slowed down to 1KB/s
|
|
|
|
size_t estimated_total_size = 0;
|
|
|
|
Random rnd(301);
|
|
|
|
for (int i = 0; i < 3000; i++) {
|
|
|
|
auto rand_num = rnd.Uniform(20);
|
|
|
|
// Spread the size range to more.
|
|
|
|
size_t entry_size = rand_num * rand_num * rand_num;
|
|
|
|
WriteOptions wo;
|
|
|
|
// For a small chance, set a timeout.
|
|
|
|
if (rnd.Uniform(20) == 6) {
|
|
|
|
wo.timeout_hint_us = 1500;
|
|
|
|
}
|
|
|
|
Put(Key(i), std::string(entry_size, 'x'), wo);
|
|
|
|
estimated_total_size += entry_size + 20;
|
|
|
|
// Ocassionally sleep a while
|
|
|
|
if (rnd.Uniform(20) == 6) {
|
|
|
|
env_->SleepForMicroseconds(2666);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uint64_t estimated_sleep_time =
|
|
|
|
estimated_total_size / options.delayed_write_rate * 1000000U;
|
|
|
|
ASSERT_GT(env_->addon_time_.load(), estimated_sleep_time * 0.8);
|
|
|
|
ASSERT_LT(env_->addon_time_.load(), estimated_sleep_time * 1.1);
|
|
|
|
|
|
|
|
env_->no_sleep_ = false;
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DBTest, SoftLimit) {
|
|
|
|
Options options;
|
|
|
|
options.env = env_;
|
|
|
|
options = CurrentOptions(options);
|
|
|
|
options.write_buffer_size = 100000; // Small write buffer
|
|
|
|
options.max_write_buffer_number = 256;
|
|
|
|
options.level0_file_num_compaction_trigger = 3;
|
|
|
|
options.level0_slowdown_writes_trigger = 3;
|
|
|
|
options.level0_stop_writes_trigger = 999999;
|
|
|
|
options.delayed_write_rate = 200000; // About 200KB/s limited rate
|
|
|
|
options.soft_rate_limit = 1.1;
|
|
|
|
options.target_file_size_base = 99999999; // All into one file
|
|
|
|
options.max_bytes_for_level_base = 50000;
|
|
|
|
options.compression = kNoCompression;
|
|
|
|
|
|
|
|
Reopen(options);
|
|
|
|
Put(Key(0), "");
|
|
|
|
|
|
|
|
// Only allow two compactions
|
|
|
|
port::Mutex mut;
|
|
|
|
port::CondVar cv(&mut);
|
|
|
|
std::atomic<int> compaction_cnt(0);
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"VersionSet::LogAndApply:WriteManifest", [&](void* arg) {
|
|
|
|
// Three flushes and the first compaction,
|
|
|
|
// three flushes and the second compaction go through.
|
|
|
|
MutexLock l(&mut);
|
|
|
|
while (compaction_cnt.load() >= 8) {
|
|
|
|
cv.Wait();
|
|
|
|
}
|
|
|
|
compaction_cnt.fetch_add(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
std::atomic<int> sleep_count(0);
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
|
|
|
"DBImpl::DelayWrite:Sleep", [&](void* arg) { sleep_count.fetch_add(1); });
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
Put(Key(i), std::string(5000, 'x'));
|
|
|
|
Put(Key(100 - i), std::string(5000, 'x'));
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
while (compaction_cnt.load() < 4 || NumTableFilesAtLevel(0) > 0) {
|
|
|
|
env_->SleepForMicroseconds(1000);
|
|
|
|
}
|
|
|
|
// Now there is one L1 file but doesn't trigger soft_rate_limit
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
|
|
|
ASSERT_EQ(sleep_count.load(), 0);
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
Put(Key(10 + i), std::string(5000, 'x'));
|
|
|
|
Put(Key(90 - i), std::string(5000, 'x'));
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
while (compaction_cnt.load() < 8 || NumTableFilesAtLevel(0) > 0) {
|
|
|
|
env_->SleepForMicroseconds(1000);
|
|
|
|
}
|
|
|
|
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
|
|
|
ASSERT_EQ(sleep_count.load(), 0);
|
|
|
|
|
|
|
|
// Slowdown is triggered now
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
Put(Key(i), std::string(100, 'x'));
|
|
|
|
}
|
|
|
|
ASSERT_GT(sleep_count.load(), 0);
|
|
|
|
|
|
|
|
{
|
|
|
|
MutexLock l(&mut);
|
|
|
|
compaction_cnt.store(7);
|
|
|
|
cv.SignalAll();
|
|
|
|
}
|
|
|
|
while (NumTableFilesAtLevel(1) > 0) {
|
|
|
|
env_->SleepForMicroseconds(1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slowdown is not triggered any more.
|
|
|
|
sleep_count.store(0);
|
|
|
|
// Slowdown is not triggered now
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
Put(Key(i), std::string(100, 'x'));
|
|
|
|
}
|
|
|
|
ASSERT_EQ(sleep_count.load(), 0);
|
|
|
|
|
|
|
|
// shrink level base so L2 will hit soft limit easier.
|
|
|
|
ASSERT_OK(dbfull()->SetOptions({
|
|
|
|
{"max_bytes_for_level_base", "5000"},
|
|
|
|
}));
|
|
|
|
compaction_cnt.store(7);
|
|
|
|
Flush();
|
|
|
|
|
|
|
|
while (NumTableFilesAtLevel(0) == 0) {
|
|
|
|
env_->SleepForMicroseconds(1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slowdown is triggered now
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
Put(Key(i), std::string(100, 'x'));
|
|
|
|
}
|
|
|
|
ASSERT_GT(sleep_count.load(), 0);
|
|
|
|
|
|
|
|
{
|
|
|
|
MutexLock l(&mut);
|
|
|
|
compaction_cnt.store(7);
|
|
|
|
cv.SignalAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
while (NumTableFilesAtLevel(2) != 0) {
|
|
|
|
env_->SleepForMicroseconds(1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slowdown is not triggered anymore
|
|
|
|
sleep_count.store(0);
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
Put(Key(i), std::string(100, 'x'));
|
|
|
|
}
|
|
|
|
ASSERT_EQ(sleep_count.load(), 0);
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
|
|
|
}
|
|
|
|
|
2013-10-04 06:49:15 +02:00
|
|
|
} // namespace rocksdb
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
Make Compaction class easier to use
Summary:
The goal of this diff is to make Compaction class easier to use. This should also make new compaction algorithms easier to write (like CompactFiles from @yhchiang and dynamic leveled and multi-leveled universal from @sdong).
Here are couple of things demonstrating that Compaction class is hard to use:
1. we have two constructors of Compaction class
2. there's this thing called grandparents_, but it appears to only be setup for leveled compaction and not compactfiles
3. it's easy to introduce a subtle and dangerous bug like this: D36225
4. SetupBottomMostLevel() is hard to understand and it shouldn't be. See this comment: https://github.com/facebook/rocksdb/blob/afbafeaeaebfd27a0f3e992fee8e0c57d07658fa/db/compaction.cc#L236-L241. It also made it harder for @yhchiang to write CompactFiles, as evidenced by this: https://github.com/facebook/rocksdb/blob/afbafeaeaebfd27a0f3e992fee8e0c57d07658fa/db/compaction_picker.cc#L204-L210
The problem is that we create Compaction object, which holds a lot of state, and then pass it around to some functions. After those functions are done mutating, then we call couple of functions on Compaction object, like SetupBottommostLevel() and MarkFilesBeingCompacted(). It is very hard to see what's happening with all that Compaction's state while it's travelling across different functions. If you're writing a new PickCompaction() function you need to try really hard to understand what are all the functions you need to run on Compaction object and what state you need to setup.
My proposed solution is to make important parts of Compaction immutable after construction. PickCompaction() should calculate compaction inputs and then pass them onto Compaction object once they are finalized. That makes it easy to create a new compaction -- just provide all the parameters to the constructor and you're done. No need to call confusing functions after you created your object.
This diff doesn't fully achieve that goal, but it comes pretty close. Here are some of the changes:
* have one Compaction constructor instead of two.
* inputs_ is constant after construction
* MarkFilesBeingCompacted() is now private to Compaction class and automatically called on construction/destruction.
* SetupBottommostLevel() is gone. Compaction figures it out on its own based on the input.
* CompactionPicker's functions are not passing around Compaction object anymore. They are only passing around the state that they need.
Test Plan:
make check
make asan_check
make valgrind_check
Reviewers: rven, anthony, sdong, yhchiang
Reviewed By: yhchiang
Subscribers: sdong, yhchiang, dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D36687
2015-04-11 00:01:54 +02:00
|
|
|
rocksdb::port::InstallStackTraceHandler();
|
2015-03-17 22:08:00 +01:00
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
return RUN_ALL_TESTS();
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|