// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. // Copyright (c) 2016, Red Hat, Inc. All rights reserved. // This source code is licensed under both the GPLv2 (found in the // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). #ifndef ROCKSDB_LITE #include "rocksdb/utilities/env_librados.h" #include #include "env/mock_env.h" #include "test_util/testharness.h" #include "rocksdb/db.h" #include "rocksdb/slice.h" #include "rocksdb/options.h" #include "util/random.h" #include #include #include "rocksdb/utilities/transaction_db.h" class Timer { typedef std::chrono::high_resolution_clock high_resolution_clock; typedef std::chrono::milliseconds milliseconds; public: explicit Timer(bool run = false) { if (run) Reset(); } void Reset() { _start = high_resolution_clock::now(); } milliseconds Elapsed() const { return std::chrono::duration_cast(high_resolution_clock::now() - _start); } template friend std::basic_ostream& operator<<(std::basic_ostream& out, const Timer& timer) { return out << timer.Elapsed().count(); } private: high_resolution_clock::time_point _start; }; namespace ROCKSDB_NAMESPACE { class EnvLibradosTest : public testing::Test { public: // we will use all of these below const std::string db_name = "env_librados_test_db"; const std::string db_pool = db_name + "_pool"; const char *keyring = "admin"; const char *config = "../ceph/src/ceph.conf"; EnvLibrados* env_; const EnvOptions soptions_; EnvLibradosTest() : env_(new EnvLibrados(db_name, config, db_pool)) { } ~EnvLibradosTest() { delete env_; librados::Rados rados; int ret = 0; do { ret = rados.init("admin"); // just use the client.admin keyring if (ret < 0) { // let's handle any error that might have come back std::cerr << "couldn't initialize rados! error " << ret << std::endl; ret = EXIT_FAILURE; break; } ret = rados.conf_read_file(config); if (ret < 0) { // This could fail if the config file is malformed, but it'd be hard. std::cerr << "failed to parse config file " << config << "! error" << ret << std::endl; ret = EXIT_FAILURE; break; } /* * next, we actually connect to the cluster */ ret = rados.connect(); if (ret < 0) { std::cerr << "couldn't connect to cluster! error " << ret << std::endl; ret = EXIT_FAILURE; break; } /* * And now we're done, so let's remove our pool and then * shut down the connection gracefully. */ int delete_ret = rados.pool_delete(db_pool.c_str()); if (delete_ret < 0) { // be careful not to std::cerr << "We failed to delete our test pool!" << db_pool << delete_ret << std::endl; ret = EXIT_FAILURE; } } while (0); } }; TEST_F(EnvLibradosTest, Basics) { uint64_t file_size; std::unique_ptr writable_file; std::vector children; ASSERT_OK(env_->CreateDir("/dir")); // Check that the directory is empty. ASSERT_EQ(Status::NotFound(), env_->FileExists("/dir/non_existent")); ASSERT_TRUE(!env_->GetFileSize("/dir/non_existent", &file_size).ok()); ASSERT_OK(env_->GetChildren("/dir", &children)); ASSERT_EQ(0U, children.size()); // Create a file. ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); writable_file.reset(); // Check that the file exists. ASSERT_OK(env_->FileExists("/dir/f")); ASSERT_OK(env_->GetFileSize("/dir/f", &file_size)); ASSERT_EQ(0U, file_size); ASSERT_OK(env_->GetChildren("/dir", &children)); ASSERT_EQ(1U, children.size()); ASSERT_EQ("f", children[0]); // Write to the file. ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); ASSERT_OK(writable_file->Append("abc")); writable_file.reset(); // Check for expected size. ASSERT_OK(env_->GetFileSize("/dir/f", &file_size)); ASSERT_EQ(3U, file_size); // Check that renaming works. ASSERT_TRUE(!env_->RenameFile("/dir/non_existent", "/dir/g").ok()); ASSERT_OK(env_->RenameFile("/dir/f", "/dir/g")); ASSERT_EQ(Status::NotFound(), env_->FileExists("/dir/f")); ASSERT_OK(env_->FileExists("/dir/g")); ASSERT_OK(env_->GetFileSize("/dir/g", &file_size)); ASSERT_EQ(3U, file_size); // Check that opening non-existent file fails. std::unique_ptr seq_file; std::unique_ptr rand_file; ASSERT_TRUE( !env_->NewSequentialFile("/dir/non_existent", &seq_file, soptions_).ok()); ASSERT_TRUE(!seq_file); ASSERT_TRUE(!env_->NewRandomAccessFile("/dir/non_existent", &rand_file, soptions_).ok()); ASSERT_TRUE(!rand_file); // Check that deleting works. ASSERT_TRUE(!env_->DeleteFile("/dir/non_existent").ok()); ASSERT_OK(env_->DeleteFile("/dir/g")); ASSERT_EQ(Status::NotFound(), env_->FileExists("/dir/g")); ASSERT_OK(env_->GetChildren("/dir", &children)); ASSERT_EQ(0U, children.size()); ASSERT_OK(env_->DeleteDir("/dir")); } TEST_F(EnvLibradosTest, ReadWrite) { std::unique_ptr writable_file; std::unique_ptr seq_file; std::unique_ptr rand_file; Slice result; char scratch[100]; ASSERT_OK(env_->CreateDir("/dir")); ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); ASSERT_OK(writable_file->Append("hello ")); ASSERT_OK(writable_file->Append("world")); writable_file.reset(); // Read sequentially. ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file, soptions_)); ASSERT_OK(seq_file->Read(5, &result, scratch)); // Read "hello". ASSERT_EQ(0, result.compare("hello")); ASSERT_OK(seq_file->Skip(1)); ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Read "world". ASSERT_EQ(0, result.compare("world")); ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Try reading past EOF. ASSERT_EQ(0U, result.size()); ASSERT_OK(seq_file->Skip(100)); // Try to skip past end of file. ASSERT_OK(seq_file->Read(1000, &result, scratch)); ASSERT_EQ(0U, result.size()); // Random reads. ASSERT_OK(env_->NewRandomAccessFile("/dir/f", &rand_file, soptions_)); ASSERT_OK(rand_file->Read(6, 5, &result, scratch)); // Read "world". ASSERT_EQ(0, result.compare("world")); ASSERT_OK(rand_file->Read(0, 5, &result, scratch)); // Read "hello". ASSERT_EQ(0, result.compare("hello")); ASSERT_OK(rand_file->Read(10, 100, &result, scratch)); // Read "d". ASSERT_EQ(0, result.compare("d")); // Too high offset. ASSERT_OK(rand_file->Read(1000, 5, &result, scratch)); } TEST_F(EnvLibradosTest, Locks) { FileLock* lock = nullptr; std::unique_ptr writable_file; ASSERT_OK(env_->CreateDir("/dir")); ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); // These are no-ops, but we test they return success. ASSERT_OK(env_->LockFile("some file", &lock)); ASSERT_OK(env_->UnlockFile(lock)); ASSERT_OK(env_->LockFile("/dir/f", &lock)); ASSERT_OK(env_->UnlockFile(lock)); } TEST_F(EnvLibradosTest, Misc) { std::string test_dir; ASSERT_OK(env_->GetTestDirectory(&test_dir)); ASSERT_TRUE(!test_dir.empty()); std::unique_ptr writable_file; ASSERT_TRUE(!env_->NewWritableFile("/a/b", &writable_file, soptions_).ok()); ASSERT_OK(env_->NewWritableFile("/a", &writable_file, soptions_)); // These are no-ops, but we test they return success. ASSERT_OK(writable_file->Sync()); ASSERT_OK(writable_file->Flush()); ASSERT_OK(writable_file->Close()); writable_file.reset(); } TEST_F(EnvLibradosTest, LargeWrite) { const size_t kWriteSize = 300 * 1024; char* scratch = new char[kWriteSize * 2]; std::string write_data; for (size_t i = 0; i < kWriteSize; ++i) { write_data.append(1, 'h'); } std::unique_ptr writable_file; ASSERT_OK(env_->CreateDir("/dir")); ASSERT_OK(env_->NewWritableFile("/dir/g", &writable_file, soptions_)); ASSERT_OK(writable_file->Append("foo")); ASSERT_OK(writable_file->Append(write_data)); writable_file.reset(); std::unique_ptr seq_file; Slice result; ASSERT_OK(env_->NewSequentialFile("/dir/g", &seq_file, soptions_)); ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo". ASSERT_EQ(0, result.compare("foo")); size_t read = 0; std::string read_data; while (read < kWriteSize) { ASSERT_OK(seq_file->Read(kWriteSize - read, &result, scratch)); read_data.append(result.data(), result.size()); read += result.size(); } ASSERT_TRUE(write_data == read_data); delete[] scratch; } TEST_F(EnvLibradosTest, FrequentlySmallWrite) { const size_t kWriteSize = 1 << 10; char* scratch = new char[kWriteSize * 2]; std::string write_data; for (size_t i = 0; i < kWriteSize; ++i) { write_data.append(1, 'h'); } std::unique_ptr writable_file; ASSERT_OK(env_->CreateDir("/dir")); ASSERT_OK(env_->NewWritableFile("/dir/g", &writable_file, soptions_)); ASSERT_OK(writable_file->Append("foo")); for (size_t i = 0; i < kWriteSize; ++i) { ASSERT_OK(writable_file->Append("h")); } writable_file.reset(); std::unique_ptr seq_file; Slice result; ASSERT_OK(env_->NewSequentialFile("/dir/g", &seq_file, soptions_)); ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo". ASSERT_EQ(0, result.compare("foo")); size_t read = 0; std::string read_data; while (read < kWriteSize) { ASSERT_OK(seq_file->Read(kWriteSize - read, &result, scratch)); read_data.append(result.data(), result.size()); read += result.size(); } ASSERT_TRUE(write_data == read_data); delete[] scratch; } TEST_F(EnvLibradosTest, Truncate) { const size_t kWriteSize = 300 * 1024; const size_t truncSize = 1024; std::string write_data; for (size_t i = 0; i < kWriteSize; ++i) { write_data.append(1, 'h'); } std::unique_ptr writable_file; ASSERT_OK(env_->CreateDir("/dir")); ASSERT_OK(env_->NewWritableFile("/dir/g", &writable_file, soptions_)); ASSERT_OK(writable_file->Append(write_data)); ASSERT_EQ(writable_file->GetFileSize(), kWriteSize); ASSERT_OK(writable_file->Truncate(truncSize)); ASSERT_EQ(writable_file->GetFileSize(), truncSize); writable_file.reset(); } TEST_F(EnvLibradosTest, DBBasics) { std::string kDBPath = "/tmp/DBBasics"; DB* db; Options options; // Optimize RocksDB. This is the easiest way to get RocksDB to perform well options.IncreaseParallelism(); options.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options.create_if_missing = true; options.env = env_; // open DB Status s = DB::Open(options, kDBPath, &db); assert(s.ok()); // Put key-value s = db->Put(WriteOptions(), "key1", "value"); assert(s.ok()); std::string value; // get value s = db->Get(ReadOptions(), "key1", &value); assert(s.ok()); assert(value == "value"); // atomically apply a set of updates { WriteBatch batch; batch.Delete("key1"); batch.Put("key2", value); s = db->Write(WriteOptions(), &batch); } s = db->Get(ReadOptions(), "key1", &value); assert(s.IsNotFound()); db->Get(ReadOptions(), "key2", &value); assert(value == "value"); delete db; } TEST_F(EnvLibradosTest, DBLoadKeysInRandomOrder) { char key[20] = {0}, value[20] = {0}; int max_loop = 1 << 10; Timer timer(false); std::cout << "Test size : loop(" << max_loop << ")" << std::endl; /********************************** use default env ***********************************/ std::string kDBPath1 = "/tmp/DBLoadKeysInRandomOrder1"; DB* db1; Options options1; // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well options1.IncreaseParallelism(); options1.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options1.create_if_missing = true; // open DB Status s1 = DB::Open(options1, kDBPath1, &db1); assert(s1.ok()); ROCKSDB_NAMESPACE::Random64 r1(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { snprintf(key, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); snprintf(value, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); // Put key-value s1 = db1->Put(WriteOptions(), key, value); assert(s1.ok()); } std::cout << "Time by default : " << timer << "ms" << std::endl; delete db1; /********************************** use librados env ***********************************/ std::string kDBPath2 = "/tmp/DBLoadKeysInRandomOrder2"; DB* db2; Options options2; // Optimize RocksDB. This is the easiest way to get RocksDB to perform well options2.IncreaseParallelism(); options2.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options2.create_if_missing = true; options2.env = env_; // open DB Status s2 = DB::Open(options2, kDBPath2, &db2); assert(s2.ok()); ROCKSDB_NAMESPACE::Random64 r2(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { snprintf(key, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); snprintf(value, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); // Put key-value s2 = db2->Put(WriteOptions(), key, value); assert(s2.ok()); } std::cout << "Time by librados : " << timer << "ms" << std::endl; delete db2; } TEST_F(EnvLibradosTest, DBBulkLoadKeysInRandomOrder) { char key[20] = {0}, value[20] = {0}; int max_loop = 1 << 6; int bulk_size = 1 << 15; Timer timer(false); std::cout << "Test size : loop(" << max_loop << "); bulk_size(" << bulk_size << ")" << std::endl; /********************************** use default env ***********************************/ std::string kDBPath1 = "/tmp/DBBulkLoadKeysInRandomOrder1"; DB* db1; Options options1; // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well options1.IncreaseParallelism(); options1.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options1.create_if_missing = true; // open DB Status s1 = DB::Open(options1, kDBPath1, &db1); assert(s1.ok()); ROCKSDB_NAMESPACE::Random64 r1(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); snprintf(value, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s1 = db1->Write(WriteOptions(), &batch); assert(s1.ok()); } std::cout << "Time by default : " << timer << "ms" << std::endl; delete db1; /********************************** use librados env ***********************************/ std::string kDBPath2 = "/tmp/DBBulkLoadKeysInRandomOrder2"; DB* db2; Options options2; // Optimize RocksDB. This is the easiest way to get RocksDB to perform well options2.IncreaseParallelism(); options2.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options2.create_if_missing = true; options2.env = env_; // open DB Status s2 = DB::Open(options2, kDBPath2, &db2); assert(s2.ok()); ROCKSDB_NAMESPACE::Random64 r2(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); snprintf(value, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s2 = db2->Write(WriteOptions(), &batch); assert(s2.ok()); } std::cout << "Time by librados : " << timer << "ms" << std::endl; delete db2; } TEST_F(EnvLibradosTest, DBBulkLoadKeysInSequentialOrder) { char key[20] = {0}, value[20] = {0}; int max_loop = 1 << 6; int bulk_size = 1 << 15; Timer timer(false); std::cout << "Test size : loop(" << max_loop << "); bulk_size(" << bulk_size << ")" << std::endl; /********************************** use default env ***********************************/ std::string kDBPath1 = "/tmp/DBBulkLoadKeysInSequentialOrder1"; DB* db1; Options options1; // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well options1.IncreaseParallelism(); options1.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options1.create_if_missing = true; // open DB Status s1 = DB::Open(options1, kDBPath1, &db1); assert(s1.ok()); ROCKSDB_NAMESPACE::Random64 r1(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%019lld", (long long)(i * bulk_size + j)); snprintf(value, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s1 = db1->Write(WriteOptions(), &batch); assert(s1.ok()); } std::cout << "Time by default : " << timer << "ms" << std::endl; delete db1; /********************************** use librados env ***********************************/ std::string kDBPath2 = "/tmp/DBBulkLoadKeysInSequentialOrder2"; DB* db2; Options options2; // Optimize RocksDB. This is the easiest way to get RocksDB to perform well options2.IncreaseParallelism(); options2.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options2.create_if_missing = true; options2.env = env_; // open DB Status s2 = DB::Open(options2, kDBPath2, &db2); assert(s2.ok()); ROCKSDB_NAMESPACE::Random64 r2(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); snprintf(value, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s2 = db2->Write(WriteOptions(), &batch); assert(s2.ok()); } std::cout << "Time by librados : " << timer << "ms" << std::endl; delete db2; } TEST_F(EnvLibradosTest, DBRandomRead) { char key[20] = {0}, value[20] = {0}; int max_loop = 1 << 6; int bulk_size = 1 << 10; int read_loop = 1 << 20; Timer timer(false); std::cout << "Test size : keys_num(" << max_loop << ", " << bulk_size << "); read_loop(" << read_loop << ")" << std::endl; /********************************** use default env ***********************************/ std::string kDBPath1 = "/tmp/DBRandomRead1"; DB* db1; Options options1; // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well options1.IncreaseParallelism(); options1.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options1.create_if_missing = true; // open DB Status s1 = DB::Open(options1, kDBPath1, &db1); assert(s1.ok()); ROCKSDB_NAMESPACE::Random64 r1(time(nullptr)); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%019lld", (long long)(i * bulk_size + j)); snprintf(value, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s1 = db1->Write(WriteOptions(), &batch); assert(s1.ok()); } timer.Reset(); int base1 = 0, offset1 = 0; for (int i = 0; i < read_loop; ++i) { base1 = r1.Uniform(max_loop); offset1 = r1.Uniform(bulk_size); std::string value1; snprintf(key, 20, "%019lld", (long long)(base1 * bulk_size + offset1)); s1 = db1->Get(ReadOptions(), key, &value1); assert(s1.ok()); } std::cout << "Time by default : " << timer << "ms" << std::endl; delete db1; /********************************** use librados env ***********************************/ std::string kDBPath2 = "/tmp/DBRandomRead2"; DB* db2; Options options2; // Optimize RocksDB. This is the easiest way to get RocksDB to perform well options2.IncreaseParallelism(); options2.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options2.create_if_missing = true; options2.env = env_; // open DB Status s2 = DB::Open(options2, kDBPath2, &db2); assert(s2.ok()); ROCKSDB_NAMESPACE::Random64 r2(time(nullptr)); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%019lld", (long long)(i * bulk_size + j)); snprintf(value, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s2 = db2->Write(WriteOptions(), &batch); assert(s2.ok()); } timer.Reset(); int base2 = 0, offset2 = 0; for (int i = 0; i < read_loop; ++i) { base2 = r2.Uniform(max_loop); offset2 = r2.Uniform(bulk_size); std::string value2; snprintf(key, 20, "%019lld", (long long)(base2 * bulk_size + offset2)); s2 = db2->Get(ReadOptions(), key, &value2); if (!s2.ok()) { std::cout << s2.ToString() << std::endl; } assert(s2.ok()); } std::cout << "Time by librados : " << timer << "ms" << std::endl; delete db2; } class EnvLibradosMutipoolTest : public testing::Test { public: // we will use all of these below const std::string client_name = "client.admin"; const std::string cluster_name = "ceph"; const uint64_t flags = 0; const std::string db_name = "env_librados_test_db"; const std::string db_pool = db_name + "_pool"; const std::string wal_dir = "/wal"; const std::string wal_pool = db_name + "_wal_pool"; const size_t write_buffer_size = 1 << 20; const char *keyring = "admin"; const char *config = "../ceph/src/ceph.conf"; EnvLibrados* env_; const EnvOptions soptions_; EnvLibradosMutipoolTest() { env_ = new EnvLibrados(client_name, cluster_name, flags, db_name, config, db_pool, wal_dir, wal_pool, write_buffer_size); } ~EnvLibradosMutipoolTest() { delete env_; librados::Rados rados; int ret = 0; do { ret = rados.init("admin"); // just use the client.admin keyring if (ret < 0) { // let's handle any error that might have come back std::cerr << "couldn't initialize rados! error " << ret << std::endl; ret = EXIT_FAILURE; break; } ret = rados.conf_read_file(config); if (ret < 0) { // This could fail if the config file is malformed, but it'd be hard. std::cerr << "failed to parse config file " << config << "! error" << ret << std::endl; ret = EXIT_FAILURE; break; } /* * next, we actually connect to the cluster */ ret = rados.connect(); if (ret < 0) { std::cerr << "couldn't connect to cluster! error " << ret << std::endl; ret = EXIT_FAILURE; break; } /* * And now we're done, so let's remove our pool and then * shut down the connection gracefully. */ int delete_ret = rados.pool_delete(db_pool.c_str()); if (delete_ret < 0) { // be careful not to std::cerr << "We failed to delete our test pool!" << db_pool << delete_ret << std::endl; ret = EXIT_FAILURE; } delete_ret = rados.pool_delete(wal_pool.c_str()); if (delete_ret < 0) { // be careful not to std::cerr << "We failed to delete our test pool!" << wal_pool << delete_ret << std::endl; ret = EXIT_FAILURE; } } while (0); } }; TEST_F(EnvLibradosMutipoolTest, Basics) { uint64_t file_size; std::unique_ptr writable_file; std::vector children; std::vector v = {"/tmp/dir1", "/tmp/dir2", "/tmp/dir3", "/tmp/dir4", "dir"}; for (size_t i = 0; i < v.size(); ++i) { std::string dir = v[i]; std::string dir_non_existent = dir + "/non_existent"; std::string dir_f = dir + "/f"; std::string dir_g = dir + "/g"; ASSERT_OK(env_->CreateDir(dir.c_str())); // Check that the directory is empty. ASSERT_EQ(Status::NotFound(), env_->FileExists(dir_non_existent.c_str())); ASSERT_TRUE(!env_->GetFileSize(dir_non_existent.c_str(), &file_size).ok()); ASSERT_OK(env_->GetChildren(dir.c_str(), &children)); ASSERT_EQ(0U, children.size()); // Create a file. ASSERT_OK(env_->NewWritableFile(dir_f.c_str(), &writable_file, soptions_)); writable_file.reset(); // Check that the file exists. ASSERT_OK(env_->FileExists(dir_f.c_str())); ASSERT_OK(env_->GetFileSize(dir_f.c_str(), &file_size)); ASSERT_EQ(0U, file_size); ASSERT_OK(env_->GetChildren(dir.c_str(), &children)); ASSERT_EQ(1U, children.size()); ASSERT_EQ("f", children[0]); // Write to the file. ASSERT_OK(env_->NewWritableFile(dir_f.c_str(), &writable_file, soptions_)); ASSERT_OK(writable_file->Append("abc")); writable_file.reset(); // Check for expected size. ASSERT_OK(env_->GetFileSize(dir_f.c_str(), &file_size)); ASSERT_EQ(3U, file_size); // Check that renaming works. ASSERT_TRUE(!env_->RenameFile(dir_non_existent.c_str(), dir_g.c_str()).ok()); ASSERT_OK(env_->RenameFile(dir_f.c_str(), dir_g.c_str())); ASSERT_EQ(Status::NotFound(), env_->FileExists(dir_f.c_str())); ASSERT_OK(env_->FileExists(dir_g.c_str())); ASSERT_OK(env_->GetFileSize(dir_g.c_str(), &file_size)); ASSERT_EQ(3U, file_size); // Check that opening non-existent file fails. std::unique_ptr seq_file; std::unique_ptr rand_file; ASSERT_TRUE( !env_->NewSequentialFile(dir_non_existent.c_str(), &seq_file, soptions_).ok()); ASSERT_TRUE(!seq_file); ASSERT_TRUE(!env_->NewRandomAccessFile(dir_non_existent.c_str(), &rand_file, soptions_).ok()); ASSERT_TRUE(!rand_file); // Check that deleting works. ASSERT_TRUE(!env_->DeleteFile(dir_non_existent.c_str()).ok()); ASSERT_OK(env_->DeleteFile(dir_g.c_str())); ASSERT_EQ(Status::NotFound(), env_->FileExists(dir_g.c_str())); ASSERT_OK(env_->GetChildren(dir.c_str(), &children)); ASSERT_EQ(0U, children.size()); ASSERT_OK(env_->DeleteDir(dir.c_str())); } } TEST_F(EnvLibradosMutipoolTest, DBBasics) { std::string kDBPath = "/tmp/DBBasics"; std::string walPath = "/tmp/wal"; DB* db; Options options; // Optimize RocksDB. This is the easiest way to get RocksDB to perform well options.IncreaseParallelism(); options.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options.create_if_missing = true; options.env = env_; options.wal_dir = walPath; // open DB Status s = DB::Open(options, kDBPath, &db); assert(s.ok()); // Put key-value s = db->Put(WriteOptions(), "key1", "value"); assert(s.ok()); std::string value; // get value s = db->Get(ReadOptions(), "key1", &value); assert(s.ok()); assert(value == "value"); // atomically apply a set of updates { WriteBatch batch; batch.Delete("key1"); batch.Put("key2", value); s = db->Write(WriteOptions(), &batch); } s = db->Get(ReadOptions(), "key1", &value); assert(s.IsNotFound()); db->Get(ReadOptions(), "key2", &value); assert(value == "value"); delete db; } TEST_F(EnvLibradosMutipoolTest, DBBulkLoadKeysInRandomOrder) { char key[20] = {0}, value[20] = {0}; int max_loop = 1 << 6; int bulk_size = 1 << 15; Timer timer(false); std::cout << "Test size : loop(" << max_loop << "); bulk_size(" << bulk_size << ")" << std::endl; /********************************** use default env ***********************************/ std::string kDBPath1 = "/tmp/DBBulkLoadKeysInRandomOrder1"; std::string walPath = "/tmp/wal"; DB* db1; Options options1; // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well options1.IncreaseParallelism(); options1.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options1.create_if_missing = true; // open DB Status s1 = DB::Open(options1, kDBPath1, &db1); assert(s1.ok()); ROCKSDB_NAMESPACE::Random64 r1(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); snprintf(value, 20, "%16lx", (unsigned long)r1.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s1 = db1->Write(WriteOptions(), &batch); assert(s1.ok()); } std::cout << "Time by default : " << timer << "ms" << std::endl; delete db1; /********************************** use librados env ***********************************/ std::string kDBPath2 = "/tmp/DBBulkLoadKeysInRandomOrder2"; DB* db2; Options options2; // Optimize RocksDB. This is the easiest way to get RocksDB to perform well options2.IncreaseParallelism(); options2.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options2.create_if_missing = true; options2.env = env_; options2.wal_dir = walPath; // open DB Status s2 = DB::Open(options2, kDBPath2, &db2); if (!s2.ok()) { std::cerr << s2.ToString() << std::endl; } assert(s2.ok()); ROCKSDB_NAMESPACE::Random64 r2(time(nullptr)); timer.Reset(); for (int i = 0; i < max_loop; ++i) { WriteBatch batch; for (int j = 0; j < bulk_size; ++j) { snprintf(key, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); snprintf(value, 20, "%16lx", (unsigned long)r2.Uniform(std::numeric_limits::max())); batch.Put(key, value); } s2 = db2->Write(WriteOptions(), &batch); assert(s2.ok()); } std::cout << "Time by librados : " << timer << "ms" << std::endl; delete db2; } TEST_F(EnvLibradosMutipoolTest, DBTransactionDB) { std::string kDBPath = "/tmp/DBTransactionDB"; // open DB Options options; TransactionDBOptions txn_db_options; options.create_if_missing = true; options.env = env_; TransactionDB* txn_db; Status s = TransactionDB::Open(options, txn_db_options, kDBPath, &txn_db); assert(s.ok()); WriteOptions write_options; ReadOptions read_options; TransactionOptions txn_options; std::string value; //////////////////////////////////////////////////////// // // Simple OptimisticTransaction Example ("Read Committed") // //////////////////////////////////////////////////////// // Start a transaction Transaction* txn = txn_db->BeginTransaction(write_options); assert(txn); // Read a key in this transaction s = txn->Get(read_options, "abc", &value); assert(s.IsNotFound()); // Write a key in this transaction s = txn->Put("abc", "def"); assert(s.ok()); // Read a key OUTSIDE this transaction. Does not affect txn. s = txn_db->Get(read_options, "abc", &value); // Write a key OUTSIDE of this transaction. // Does not affect txn since this is an unrelated key. If we wrote key 'abc' // here, the transaction would fail to commit. s = txn_db->Put(write_options, "xyz", "zzz"); // Commit transaction s = txn->Commit(); assert(s.ok()); delete txn; //////////////////////////////////////////////////////// // // "Repeatable Read" (Snapshot Isolation) Example // -- Using a single Snapshot // //////////////////////////////////////////////////////// // Set a snapshot at start of transaction by setting set_snapshot=true txn_options.set_snapshot = true; txn = txn_db->BeginTransaction(write_options, txn_options); const Snapshot* snapshot = txn->GetSnapshot(); // Write a key OUTSIDE of transaction s = txn_db->Put(write_options, "abc", "xyz"); assert(s.ok()); // Attempt to read a key using the snapshot. This will fail since // the previous write outside this txn conflicts with this read. read_options.snapshot = snapshot; s = txn->GetForUpdate(read_options, "abc", &value); assert(s.IsBusy()); txn->Rollback(); delete txn; // Clear snapshot from read options since it is no longer valid read_options.snapshot = nullptr; snapshot = nullptr; //////////////////////////////////////////////////////// // // "Read Committed" (Monotonic Atomic Views) Example // --Using multiple Snapshots // //////////////////////////////////////////////////////// // In this example, we set the snapshot multiple times. This is probably // only necessary if you have very strict isolation requirements to // implement. // Set a snapshot at start of transaction txn_options.set_snapshot = true; txn = txn_db->BeginTransaction(write_options, txn_options); // Do some reads and writes to key "x" read_options.snapshot = txn_db->GetSnapshot(); s = txn->Get(read_options, "x", &value); txn->Put("x", "x"); // Do a write outside of the transaction to key "y" s = txn_db->Put(write_options, "y", "y"); // Set a new snapshot in the transaction txn->SetSnapshot(); txn->SetSavePoint(); read_options.snapshot = txn_db->GetSnapshot(); // Do some reads and writes to key "y" // Since the snapshot was advanced, the write done outside of the // transaction does not conflict. s = txn->GetForUpdate(read_options, "y", &value); txn->Put("y", "y"); // Decide we want to revert the last write from this transaction. txn->RollbackToSavePoint(); // Commit. s = txn->Commit(); assert(s.ok()); delete txn; // Clear snapshot from read options since it is no longer valid read_options.snapshot = nullptr; // Cleanup delete txn_db; DestroyDB(kDBPath, options); } } // namespace ROCKSDB_NAMESPACE int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } #else #include int main(int argc, char** argv) { fprintf(stderr, "SKIPPED as EnvMirror is not supported in ROCKSDB_LITE\n"); return 0; } #endif // !ROCKSDB_LITE